logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/
commit: b1309bdb403fdbfdb0a8b076a5a13af811191ca9
parent 1dd9ba5d6fa45a8965703c96e9823ac7e41c52be
Author: Lain Soykaf <lain@lain.com>
Date:   Mon, 10 Mar 2025 18:44:17 +0400

More fixes for InstanceStatic

Diffstat:

Mconfig/config.exs2+-
Mlib/pleroma/web/plugs/instance_static.ex22++++++++++++++++++++++
Mtest/pleroma/web/plugs/instance_static_test.exs43+++++++++++++++++++++++++++++++++++++++++++
3 files changed, 66 insertions(+), 1 deletion(-)

diff --git a/config/config.exs b/config/config.exs @@ -66,7 +66,7 @@ config :pleroma, Pleroma.Upload, filename_display_max_length: 30, default_description: nil, base_url: nil, - allowed_mime_types: ["image", "audio", "video"] + allowed_mime_types: ["image", "audio", "video", "text"] config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads" diff --git a/lib/pleroma/web/plugs/instance_static.ex b/lib/pleroma/web/plugs/instance_static.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.Plugs.InstanceStatic do require Pleroma.Constants + import Plug.Conn, only: [put_resp_header: 3] @moduledoc """ This is a shim to call `Plug.Static` but with runtime `from` configuration. @@ -44,10 +45,31 @@ defmodule Pleroma.Web.Plugs.InstanceStatic do end defp call_static(conn, opts, from) do + # Prevent content-type spoofing by setting content_types: false opts = opts |> Map.put(:from, from) + |> Map.put(:content_types, false) + # Get sanitized content type before calling Plug.Static + # Include "text" to allow HTML files and other text-based content + allowed_mime_types = + Pleroma.Config.get([Pleroma.Upload, :allowed_mime_types], [ + "image", + "audio", + "video", + "text" + ]) + + conn = set_content_type(conn, %{allowed_mime_types: allowed_mime_types}, conn.request_path) + + # Call Plug.Static with our sanitized content-type Plug.Static.call(conn, opts) end + + defp set_content_type(conn, opts, filepath) do + real_mime = MIME.from_path(filepath) + clean_mime = Pleroma.Web.Plugs.Utils.get_safe_mime_type(opts, real_mime) + put_resp_header(conn, "content-type", clean_mime) + end end diff --git a/test/pleroma/web/plugs/instance_static_test.exs b/test/pleroma/web/plugs/instance_static_test.exs @@ -62,4 +62,47 @@ defmodule Pleroma.Web.Plugs.InstanceStaticTest do index = get(build_conn(), "/static/kaniini.html") assert html_response(index, 200) == "<h1>rabbit hugs as a service</h1>" end + + test "sanitizes content-types for potentially dangerous file extensions" do + # Create a file with a potentially dangerous extension (.json) + # This mimics an attacker trying to serve ActivityPub JSON with a static file + File.mkdir!(@dir <> "/static") + File.write!(@dir <> "/static/malicious.json", "{\"type\": \"ActivityPub\"}") + + # Request the malicious file + conn = get(build_conn(), "/static/malicious.json") + + # Verify the file was served (status 200) + assert conn.status == 200 + + # The content should be served, but with a sanitized content-type + content_type = + Enum.find_value(conn.resp_headers, fn + {"content-type", value} -> value + _ -> nil + end) + + # It should have been sanitized to application/octet-stream because "application" + # is not in the allowed_mime_types list + assert content_type == "application/octet-stream" + + # Create a file with an allowed extension (.jpg) + File.write!(@dir <> "/static/safe.jpg", "fake image data") + + # Request the safe file + conn = get(build_conn(), "/static/safe.jpg") + + # Verify the file was served (status 200) + assert conn.status == 200 + + # Get the content-type + content_type = + Enum.find_value(conn.resp_headers, fn + {"content-type", value} -> value + _ -> nil + end) + + # It should be preserved because "image" is in the allowed_mime_types list + assert content_type == "image/jpeg" + end end