mirror of
https://git.pleroma.social/pleroma/pleroma.git
synced 2025-03-29 23:05:29 +00:00
Sanitize media uploads.
This commit is contained in:
parent
b469b9d9d3
commit
1dd9ba5d6f
3 changed files with 113 additions and 9 deletions
|
@ -83,7 +83,7 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do
|
||||||
Map.get(opts, :static_plug_opts)
|
Map.get(opts, :static_plug_opts)
|
||||||
|> Map.put(:at, [@path])
|
|> Map.put(:at, [@path])
|
||||||
|> Map.put(:from, directory)
|
|> Map.put(:from, directory)
|
||||||
|> Map.put(:content_type, false)
|
|> Map.put(:content_types, false)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -227,4 +227,93 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do
|
||||||
|> json_response_and_validate_schema(403)
|
|> json_response_and_validate_schema(403)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "Content-Type sanitization" do
|
||||||
|
setup do: oauth_access(["write:media", "read:media"])
|
||||||
|
|
||||||
|
setup do
|
||||||
|
ConfigMock
|
||||||
|
|> stub_with(Pleroma.Test.StaticConfig)
|
||||||
|
|
||||||
|
config =
|
||||||
|
Pleroma.Config.get([Pleroma.Upload])
|
||||||
|
|> Keyword.put(:uploader, Pleroma.Uploaders.Local)
|
||||||
|
|
||||||
|
clear_config([Pleroma.Upload], config)
|
||||||
|
clear_config([Pleroma.Upload, :allowed_mime_types], ["image", "audio", "video"])
|
||||||
|
|
||||||
|
# Create a file with a malicious content type and dangerous extension
|
||||||
|
malicious_file = %Plug.Upload{
|
||||||
|
content_type: "application/activity+json",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
# JSON extension to make MIME.from_path detect application/json
|
||||||
|
filename: "malicious.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
[malicious_file: malicious_file]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "sanitizes malicious content types when serving media", %{
|
||||||
|
conn: conn,
|
||||||
|
malicious_file: malicious_file
|
||||||
|
} do
|
||||||
|
# First upload the file with the malicious content type
|
||||||
|
media =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "multipart/form-data")
|
||||||
|
|> post("/api/v1/media", %{"file" => malicious_file})
|
||||||
|
|> json_response_and_validate_schema(:ok)
|
||||||
|
|
||||||
|
# Get the file URL from the response
|
||||||
|
url = media["url"]
|
||||||
|
|
||||||
|
# Now make a direct request to the media URL and check the content-type header
|
||||||
|
response =
|
||||||
|
build_conn()
|
||||||
|
|> get(URI.parse(url).path)
|
||||||
|
|
||||||
|
# Find the content-type header
|
||||||
|
content_type_header =
|
||||||
|
Enum.find(response.resp_headers, fn {name, _} -> name == "content-type" end)
|
||||||
|
|
||||||
|
# The server should detect the application/json MIME type from the .json extension
|
||||||
|
# and replace it with application/octet-stream since it's not in allowed_mime_types
|
||||||
|
assert content_type_header == {"content-type", "application/octet-stream"}
|
||||||
|
|
||||||
|
# Verify that the file was still served correctly
|
||||||
|
assert response.status == 200
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows safe content types", %{conn: conn} do
|
||||||
|
safe_image = %Plug.Upload{
|
||||||
|
content_type: "image/jpeg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "safe_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Upload a file with a safe content type
|
||||||
|
media =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "multipart/form-data")
|
||||||
|
|> post("/api/v1/media", %{"file" => safe_image})
|
||||||
|
|> json_response_and_validate_schema(:ok)
|
||||||
|
|
||||||
|
# Get the file URL from the response
|
||||||
|
url = media["url"]
|
||||||
|
|
||||||
|
# Make a direct request to the media URL and check the content-type header
|
||||||
|
response =
|
||||||
|
build_conn()
|
||||||
|
|> get(URI.parse(url).path)
|
||||||
|
|
||||||
|
# The server should preserve the image/jpeg MIME type since it's allowed
|
||||||
|
content_type_header =
|
||||||
|
Enum.find(response.resp_headers, fn {name, _} -> name == "content-type" end)
|
||||||
|
|
||||||
|
assert content_type_header == {"content-type", "image/jpeg"}
|
||||||
|
|
||||||
|
# Verify that the file was served correctly
|
||||||
|
assert response.status == 200
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,17 +3,10 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.Plugs.UploadedMediaTest do
|
defmodule Pleroma.Web.Plugs.UploadedMediaTest do
|
||||||
use Pleroma.Web.ConnCase, async: false
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
alias Pleroma.StaticStubbedConfigMock
|
|
||||||
alias Pleroma.Web.Plugs.Utils
|
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
|
describe "content-type sanitization with Utils.get_safe_mime_type/2" do
|
||||||
test "it allows safe MIME types" do
|
test "it allows safe MIME types" do
|
||||||
opts = %{allowed_mime_types: ["image", "audio", "video"]}
|
opts = %{allowed_mime_types: ["image", "audio", "video"]}
|
||||||
|
@ -34,5 +27,27 @@ defmodule Pleroma.Web.Plugs.UploadedMediaTest do
|
||||||
assert Utils.get_safe_mime_type(opts, "application/javascript") ==
|
assert Utils.get_safe_mime_type(opts, "application/javascript") ==
|
||||||
"application/octet-stream"
|
"application/octet-stream"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it sanitizes ActivityPub 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, "application/ld+json") == "application/octet-stream"
|
||||||
|
assert Utils.get_safe_mime_type(opts, "application/jrd+json") == "application/octet-stream"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it sanitizes other potentially dangerous types" do
|
||||||
|
opts = %{allowed_mime_types: ["image", "audio", "video"]}
|
||||||
|
|
||||||
|
assert Utils.get_safe_mime_type(opts, "text/html") == "application/octet-stream"
|
||||||
|
|
||||||
|
assert Utils.get_safe_mime_type(opts, "application/javascript") ==
|
||||||
|
"application/octet-stream"
|
||||||
|
|
||||||
|
assert Utils.get_safe_mime_type(opts, "text/javascript") == "application/octet-stream"
|
||||||
|
assert Utils.get_safe_mime_type(opts, "application/xhtml+xml") == "application/octet-stream"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue