commit: 51c1d6fb2dd91a1a1ac11fed0f0a4211719e30b8
parent b0c2ec5fb9ca1908dddbc66260861d4743b991b7
Author: Lain Soykaf <lain@lain.com>
Date: Tue, 11 Mar 2025 16:37:17 +0400
Containment: Never fetch locally
Diffstat:
4 files changed, 26 insertions(+), 0 deletions(-)
diff --git a/changelog.d/local-fetch-prevention.security b/changelog.d/local-fetch-prevention.security
@@ -0,0 +1 @@
+Security: Block attempts to fetch activities from the local instance to prevent spoofing.
+\ No newline at end of file
diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex
@@ -48,6 +48,19 @@ defmodule Pleroma.Object.Containment do
defp compare_uris(_id_uri, _other_uri), do: :error
@doc """
+ Checks whether an URL to fetch from is from the local server.
+
+ We never want to fetch from ourselves; if it's not in the database
+ it can't be authentic and must be a counterfeit.
+ """
+ def contain_local_fetch(id) do
+ case compare_uris(URI.parse(id), Pleroma.Web.Endpoint.struct_url()) do
+ :ok -> :error
+ _ -> :ok
+ end
+ end
+
+ @doc """
Checks that an imported AP object's actor matches the host it came from.
"""
def contain_origin(_id, %{"actor" => nil}), do: :error
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
@@ -148,6 +148,7 @@ defmodule Pleroma.Object.Fetcher do
with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
{_, true} <- {:mrf, MRF.id_filter(id)},
+ {_, :ok} <- {:local_fetch, Containment.contain_local_fetch(id)},
{:ok, body} <- get_object(id),
{:ok, data} <- safe_json_decode(body),
:ok <- Containment.contain_origin_from_id(id, data) do
@@ -160,6 +161,9 @@ defmodule Pleroma.Object.Fetcher do
{:scheme, _} ->
{:error, "Unsupported URI scheme"}
+ {:local_fetch, _} ->
+ {:error, "Trying to fetch local resource"}
+
{:error, e} ->
{:error, e}
diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs
@@ -166,6 +166,13 @@ defmodule Pleroma.Object.FetcherTest do
)
end
+ test "it does not fetch from local instance" do
+ local_url = Pleroma.Web.Endpoint.url() <> "/objects/local_resource"
+
+ assert {:fetch, {:error, "Trying to fetch local resource"}} =
+ Fetcher.fetch_object_from_id(local_url)
+ end
+
test "it validates content-type headers according to ActivityPub spec" do
# Setup a mock for an object with invalid content-type
mock(fn