From 8ed8dbcdb33958477857bdb0d9ffabb99deb9eb3 Mon Sep 17 00:00:00 2001
From: Chizu <Chizu@youjo.love>
Date: Tue, 2 Apr 2024 04:30:05 +0900
Subject: [PATCH] Sanitise Content-Type of uploads

---
 config/config.exs                       |  3 ++-
 config/description.exs                  | 13 +++++++++++++
 lib/pleroma/web/plugs/uploaded_media.ex | 22 ++++++++++++++++++++--
 3 files changed, 35 insertions(+), 3 deletions(-)

diff --git a/config/config.exs b/config/config.exs
index e11c774e5..876d25c26 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -66,7 +66,8 @@ config :pleroma, Pleroma.Upload,
   proxy_remote: false,
   filename_display_max_length: 30,
   default_description: nil,
-  base_url: nil
+  base_url: nil,
+  allowed_mime_types: ["image", "audio", "video"]
 
 config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads"
 
diff --git a/config/description.exs b/config/description.exs
index 6cceaeb51..53e26bcb8 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -105,6 +105,19 @@ config :pleroma, :config_description, [
           "https://cdn-host.com"
         ]
       },
+      %{
+        key: :allowed_mime_types,
+        label: "Allowed MIME types",
+        type: {:list, :string},
+        description:
+          "List of MIME (main) types uploads are allowed to identify themselves with. Other types may still be uploaded, but will identify as a generic binary to clients. WARNING: Loosening this over the defaults can lead to security issues. Removing types is safe, but only add to the list if you are sure you know what you are doing.",
+        suggestions: [
+          "image",
+          "audio",
+          "video",
+          "font"
+        ]
+      },
       %{
         key: :proxy_remote,
         type: :boolean,
diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex
index 72f20e8de..8a7ff54e4 100644
--- a/lib/pleroma/web/plugs/uploaded_media.ex
+++ b/lib/pleroma/web/plugs/uploaded_media.ex
@@ -28,7 +28,9 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do
       |> Keyword.put(:at, "/__unconfigured_media_plug")
       |> Plug.Static.init()
 
-    %{static_plug_opts: static_plug_opts}
+      allowed_mime_types = Pleroma.Config.get([Pleroma.Upload, :allowed_mime_types])
+
+      %{static_plug_opts: static_plug_opts, allowed_mime_types: allowed_mime_types}
   end
 
   def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
@@ -68,13 +70,29 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do
 
   defp media_is_banned(_, _), do: false
 
+  defp get_safe_mime_type(%{allowed_mime_types: allowed_mime_types} = _opts, mime) do
+    [maintype | _] = String.split(mime, "/", parts: 2)
+    if maintype in allowed_mime_types, do: mime, else: "application/octet-stream"
+  end
+
+  defp set_content_type(conn, opts, filepath) do
+    real_mime = MIME.from_path(filepath)
+    clean_mime = get_safe_mime_type(opts, real_mime)
+    put_resp_header(conn, "content-type", clean_mime)
+  end
+
+
   defp get_media(conn, {:static_dir, directory}, opts) do
     static_opts =
       Map.get(opts, :static_plug_opts)
       |> Map.put(:at, [@path])
       |> Map.put(:from, directory)
+      |> Map.put(:set_content_type, false)
 
-    conn = Plug.Static.call(conn, static_opts)
+    conn =
+      conn
+      |> set_content_type(opts, conn.request_path)
+      |> Pleroma.Web.Plugs.StaticNoCT.call(static_opts)
 
     if conn.halted do
       conn