fix(federation): reject actors that point at other hosts
A remote actor could set its inbox or shared inbox to a third-party host, so our signed deliveries went there. It could also claim another actor's followers collection, which the visibility checks trust. Validate fetched actors and incoming actor Updates: inbox, endpoints.sharedInbox, followers, following, featured and the publicKey id must be on the same host as the actor id. Based on Akkoma f07eb4cb5. The Bridgy fixture gets the key id that current Bridgy Fed serves.
This commit is contained in:
parent
d54ff52691
commit
695dd340de
9 changed files with 201 additions and 8 deletions
|
|
@ -112,4 +112,14 @@ defmodule Pleroma.Object.Containment do
|
|||
do: contain_origin(id, object)
|
||||
|
||||
def contain_child(_), do: :ok
|
||||
|
||||
@doc "Checks that two URIs are on the same host. URIs without a host never match."
|
||||
def same_origin(id, other_id) when is_binary(id) and is_binary(other_id) do
|
||||
case {URI.parse(id), URI.parse(other_id)} do
|
||||
{%URI{host: host}, %URI{host: host}} when is_binary(host) and host != "" -> :ok
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def same_origin(_id, _other_id), do: :error
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
alias Pleroma.Upload
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UserValidator
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.Streamer
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
|
@ -1924,6 +1925,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
def user_data_from_user_object(data, additional \\ []) do
|
||||
with {:ok, data} <- MRF.filter(data),
|
||||
{:ok, data, _} <- UserValidator.validate(data, []),
|
||||
{:ok, data} <- object_to_user_data(data, additional) do
|
||||
{:ok, data}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
|
||||
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
|
||||
|
||||
import Pleroma.Constants, only: [activity_types: 0, object_types: 0]
|
||||
import Pleroma.Constants, only: [activity_types: 0, actor_types: 0, object_types: 0]
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
|
|
@ -37,6 +37,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UserValidator
|
||||
|
||||
@impl true
|
||||
def validate(object, meta)
|
||||
|
|
@ -226,7 +227,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
end
|
||||
|
||||
def validate(%{"type" => type} = object, meta) when type == "Update" do
|
||||
with {:ok, object} <-
|
||||
with :ok <- validate_updated_actor(object, meta),
|
||||
{:ok, object} <-
|
||||
object
|
||||
|> UpdateValidator.cast_and_validate(meta)
|
||||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
|
|
@ -247,6 +249,19 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
|
||||
def validate(o, m), do: {:error, {:validator_not_set, {o, m}}}
|
||||
|
||||
defp validate_updated_actor(%{"object" => %{"type" => type} = actor}, meta)
|
||||
when type in actor_types() do
|
||||
with {_, false} <- {:local, Access.get(meta, :local, false)},
|
||||
{:ok, _, _} <- UserValidator.validate(actor, meta) do
|
||||
:ok
|
||||
else
|
||||
{:local, true} -> :ok
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_updated_actor(_, _), do: :ok
|
||||
|
||||
def cast_and_apply_and_stringify_with_history(object) do
|
||||
do_separate_with_history(object, fn object ->
|
||||
with {:ok, object_data} <- cast_and_apply(object),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidator do
|
||||
@moduledoc """
|
||||
Checks that a remote actor document only points at its own host.
|
||||
|
||||
We deliver to the inbox and shared inbox, use the collections for
|
||||
addressing and visibility checks, and map signature key ids back to
|
||||
the actor. If an actor could point any of these at another host, it
|
||||
could redirect our signed deliveries or claim another actor's
|
||||
collections. Optional fields are only checked when present.
|
||||
|
||||
Based on Akkoma's UserValidator.
|
||||
"""
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
|
||||
|
||||
alias Pleroma.Object.Containment
|
||||
|
||||
@collections ["inbox", "followers", "following", "featured"]
|
||||
|
||||
@impl true
|
||||
def validate(%{"id" => id} = data, meta) when is_binary(id) do
|
||||
fields =
|
||||
Enum.map(@collections, &{&1, data[&1]}) ++
|
||||
[
|
||||
{"sharedInbox", get_in_map(data, ["endpoints", "sharedInbox"])},
|
||||
{"publicKey", get_in_map(data, ["publicKey", "id"])}
|
||||
]
|
||||
|
||||
case Enum.find(fields, fn {_, value} -> not same_origin?(id, value) end) do
|
||||
nil -> {:ok, data, meta}
|
||||
{field, _} -> {:error, "#{field} is not on the same host as the actor"}
|
||||
end
|
||||
end
|
||||
|
||||
def validate(_data, _meta), do: {:error, "Actor has no id"}
|
||||
|
||||
defp same_origin?(_id, nil), do: true
|
||||
defp same_origin?(id, %{"id" => value}), do: same_origin?(id, value)
|
||||
defp same_origin?(id, value), do: Containment.same_origin(id, value) == :ok
|
||||
|
||||
defp get_in_map(data, [key | rest]) do
|
||||
case data do
|
||||
%{^key => value} when rest == [] -> value
|
||||
%{^key => value} -> get_in_map(value, rest)
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue