diff --git a/changelog.d/content-type-sanitize.security b/changelog.d/content-type-sanitize.security
new file mode 100644
index 000000000..a70b49f35
--- /dev/null
+++ b/changelog.d/content-type-sanitize.security
@@ -0,0 +1 @@
+Fix content-type spoofing vulnerability that could allow users to upload ActivityPub objects as attachments
\ No newline at end of file
diff --git a/config/config.exs b/config/config.exs
index 643f15414..50672cfc8 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -65,7 +65,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 f091e4924..996978298 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -117,6 +117,19 @@ config :pleroma, :config_description, [
key: :filename_display_max_length,
type: :integer,
description: "Set max length of a filename to display. 0 = no limit. Default: 30"
+ },
+ %{
+ 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"
+ ]
}
]
},
diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex
index f1076da1b..3c5f086f7 100644
--- a/lib/pleroma/web/plugs/uploaded_media.ex
+++ b/lib/pleroma/web/plugs/uploaded_media.ex
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do
require Logger
alias Pleroma.Web.MediaProxy
+ alias Pleroma.Web.Plugs.Utils
@behaviour Plug
# no slashes
@@ -28,7 +29,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
@@ -69,13 +72,23 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do
defp media_is_banned(_, _), do: false
+ defp set_content_type(conn, opts, filepath) do
+ real_mime = MIME.from_path(filepath)
+ clean_mime = Utils.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(:content_type, false)
- conn = Plug.Static.call(conn, static_opts)
+ conn =
+ conn
+ |> set_content_type(opts, conn.request_path)
+ |> Plug.Static.call(static_opts)
if conn.halted do
conn
diff --git a/lib/pleroma/web/plugs/utils.ex b/lib/pleroma/web/plugs/utils.ex
new file mode 100644
index 000000000..05e0fbe84
--- /dev/null
+++ b/lib/pleroma/web/plugs/utils.ex
@@ -0,0 +1,14 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.Utils do
+ @moduledoc """
+ Some helper functions shared across several plugs
+ """
+
+ def 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
+end
diff --git a/test/pleroma/web/plugs/uploaded_media_test.exs b/test/pleroma/web/plugs/uploaded_media_test.exs
new file mode 100644
index 000000000..b260fd03b
--- /dev/null
+++ b/test/pleroma/web/plugs/uploaded_media_test.exs
@@ -0,0 +1,38 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UploadedMediaTest do
+ use Pleroma.Web.ConnCase, async: false
+
+ alias Pleroma.StaticStubbedConfigMock
+ alias Pleroma.Web.Plugs.Utils
+
+ setup do
+ Mox.stub_with(StaticStubbedConfigMock, Pleroma.Test.StaticConfig)
+
+ {:ok, %{}}
+ end
+
+ describe "content-type sanitization with Utils.get_safe_mime_type/2" do
+ test "it allows safe MIME types" do
+ opts = %{allowed_mime_types: ["image", "audio", "video"]}
+
+ assert Utils.get_safe_mime_type(opts, "image/jpeg") == "image/jpeg"
+ assert Utils.get_safe_mime_type(opts, "audio/mpeg") == "audio/mpeg"
+ assert Utils.get_safe_mime_type(opts, "video/mp4") == "video/mp4"
+ end
+
+ test "it sanitizes potentially dangerous content-types" do
+ opts = %{allowed_mime_types: ["image", "audio", "video"]}
+
+ assert Utils.get_safe_mime_type(opts, "application/activity+json") ==
+ "application/octet-stream"
+
+ assert Utils.get_safe_mime_type(opts, "text/html") == "application/octet-stream"
+
+ assert Utils.get_safe_mime_type(opts, "application/javascript") ==
+ "application/octet-stream"
+ end
+ end
+end