check if data is visible before embedding it in OG tags
previously we would uncritically take data and format it into tags for static-fe and the like - however, instances can be configured to disallow unauthenticated access to these resources. this means that OG tags as a vector for information leakage. _technically_ this should only occur if you have both restrict_unauthenticated *AND* you run static-fe, which makes no sense since static-fe is for unauthenticated people in particular, but hey ho.
This commit is contained in:
		
							parent
							
								
									1135935cbe
								
							
						
					
					
						commit
						05f8179d08
					
				
					 6 changed files with 115 additions and 25 deletions
				
			
		| 
						 | 
				
			
			@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 | 
			
		|||
 | 
			
		||||
## Fixed
 | 
			
		||||
- Issue preventing fetching anything from IPv6-only instances
 | 
			
		||||
- Issue allowing post content to leak via opengraph tags despite :estrict\_unauthenticated being set
 | 
			
		||||
 | 
			
		||||
## 2024.03
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,14 +12,38 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
 | 
			
		|||
  @behaviour Provider
 | 
			
		||||
  @media_types ["image", "audio", "video"]
 | 
			
		||||
 | 
			
		||||
  defp user_avatar_tags(user) do
 | 
			
		||||
    if Utils.visible?(user) do
 | 
			
		||||
      [
 | 
			
		||||
        {:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))],
 | 
			
		||||
         []},
 | 
			
		||||
        {:meta, [property: "og:image:width", content: 150], []},
 | 
			
		||||
        {:meta, [property: "og:image:height", content: 150], []}
 | 
			
		||||
      ]
 | 
			
		||||
    else
 | 
			
		||||
      []
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  @impl Provider
 | 
			
		||||
  def build_tags(%{
 | 
			
		||||
        object: object,
 | 
			
		||||
        url: url,
 | 
			
		||||
        user: user
 | 
			
		||||
      }) do
 | 
			
		||||
    attachments = build_attachments(object)
 | 
			
		||||
    scrubbed_content = Utils.scrub_html_and_truncate(object)
 | 
			
		||||
    attachments =
 | 
			
		||||
      if Utils.visible?(object) do
 | 
			
		||||
        build_attachments(object)
 | 
			
		||||
      else
 | 
			
		||||
        []
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    scrubbed_content =
 | 
			
		||||
      if Utils.visible?(object) do
 | 
			
		||||
        Utils.scrub_html_and_truncate(object)
 | 
			
		||||
      else
 | 
			
		||||
        "Content cannot be displayed."
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    [
 | 
			
		||||
      {:meta,
 | 
			
		||||
| 
						 | 
				
			
			@ -36,12 +60,7 @@ def build_tags(%{
 | 
			
		|||
      {:meta, [property: "og:type", content: "article"], []}
 | 
			
		||||
    ] ++
 | 
			
		||||
      if attachments == [] or Metadata.activity_nsfw?(object) do
 | 
			
		||||
        [
 | 
			
		||||
          {:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))],
 | 
			
		||||
           []},
 | 
			
		||||
          {:meta, [property: "og:image:width", content: 150], []},
 | 
			
		||||
          {:meta, [property: "og:image:height", content: 150], []}
 | 
			
		||||
        ]
 | 
			
		||||
        user_avatar_tags(user)
 | 
			
		||||
      else
 | 
			
		||||
        attachments
 | 
			
		||||
      end
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +68,9 @@ def build_tags(%{
 | 
			
		|||
 | 
			
		||||
  @impl Provider
 | 
			
		||||
  def build_tags(%{user: user}) do
 | 
			
		||||
    with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
 | 
			
		||||
    if Utils.visible?(user) do
 | 
			
		||||
      truncated_bio = Utils.scrub_html_and_truncate(user.bio)
 | 
			
		||||
 | 
			
		||||
      [
 | 
			
		||||
        {:meta,
 | 
			
		||||
         [
 | 
			
		||||
| 
						 | 
				
			
			@ -58,12 +79,10 @@ def build_tags(%{user: user}) do
 | 
			
		|||
         ], []},
 | 
			
		||||
        {:meta, [property: "og:url", content: user.uri || user.ap_id], []},
 | 
			
		||||
        {:meta, [property: "og:description", content: truncated_bio], []},
 | 
			
		||||
        {:meta, [property: "og:type", content: "article"], []},
 | 
			
		||||
        {:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))],
 | 
			
		||||
         []},
 | 
			
		||||
        {:meta, [property: "og:image:width", content: 150], []},
 | 
			
		||||
        {:meta, [property: "og:image:height", content: 150], []}
 | 
			
		||||
      ]
 | 
			
		||||
        {:meta, [property: "og:type", content: "article"], []}
 | 
			
		||||
      ] ++ user_avatar_tags(user)
 | 
			
		||||
    else
 | 
			
		||||
      []
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,8 +17,19 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
 | 
			
		|||
 | 
			
		||||
  @impl Provider
 | 
			
		||||
  def build_tags(%{activity_id: id, object: object, user: user}) do
 | 
			
		||||
    attachments = build_attachments(id, object)
 | 
			
		||||
    scrubbed_content = Utils.scrub_html_and_truncate(object)
 | 
			
		||||
    attachments =
 | 
			
		||||
      if Utils.visible?(object) do
 | 
			
		||||
        build_attachments(id, object)
 | 
			
		||||
      else
 | 
			
		||||
        []
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    scrubbed_content =
 | 
			
		||||
      if Utils.visible?(object) do
 | 
			
		||||
        Utils.scrub_html_and_truncate(object)
 | 
			
		||||
      else
 | 
			
		||||
        "Content cannot be displayed."
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
    [
 | 
			
		||||
      title_tag(user),
 | 
			
		||||
| 
						 | 
				
			
			@ -36,13 +47,17 @@ def build_tags(%{activity_id: id, object: object, user: user}) do
 | 
			
		|||
 | 
			
		||||
  @impl Provider
 | 
			
		||||
  def build_tags(%{user: user}) do
 | 
			
		||||
    with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
 | 
			
		||||
      [
 | 
			
		||||
        title_tag(user),
 | 
			
		||||
        {:meta, [name: "twitter:description", content: truncated_bio], []},
 | 
			
		||||
        image_tag(user),
 | 
			
		||||
        {:meta, [name: "twitter:card", content: "summary"], []}
 | 
			
		||||
      ]
 | 
			
		||||
    if Utils.visible?(user) do
 | 
			
		||||
      with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
 | 
			
		||||
        [
 | 
			
		||||
          title_tag(user),
 | 
			
		||||
          {:meta, [name: "twitter:description", content: truncated_bio], []},
 | 
			
		||||
          image_tag(user),
 | 
			
		||||
          {:meta, [name: "twitter:card", content: "summary"], []}
 | 
			
		||||
        ]
 | 
			
		||||
      end
 | 
			
		||||
    else
 | 
			
		||||
      []
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +66,11 @@ defp title_tag(user) do
 | 
			
		|||
  end
 | 
			
		||||
 | 
			
		||||
  def image_tag(user) do
 | 
			
		||||
    {:meta, [name: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))], []}
 | 
			
		||||
    if Utils.visible?(user) do
 | 
			
		||||
      {:meta, [name: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))], []}
 | 
			
		||||
    else
 | 
			
		||||
      {:meta, [name: "twitter:image", content: ""], []}
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,15 @@ defmodule Pleroma.Web.Metadata.Utils do
 | 
			
		|||
  alias Pleroma.Emoji
 | 
			
		||||
  alias Pleroma.Formatter
 | 
			
		||||
  alias Pleroma.HTML
 | 
			
		||||
  alias Pleroma.Web.ActivityPub.Visibility
 | 
			
		||||
 | 
			
		||||
  def visible?(%Pleroma.User{} = object) do
 | 
			
		||||
    Visibility.restrict_unauthenticated_access?(object) == :visible
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def visible?(object) do
 | 
			
		||||
    Visibility.visible_for_user?(object, nil)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  defp scrub_html_and_truncate_object_field(field, object) do
 | 
			
		||||
    field
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,8 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraphTest do
 | 
			
		|||
 | 
			
		||||
  setup do: clear_config([Pleroma.Web.Metadata, :unfurl_nsfw])
 | 
			
		||||
  setup do: clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
 | 
			
		||||
  setup do: clear_config([:restrict_unauthenticated, :profiles, :local])
 | 
			
		||||
  setup do: clear_config([:restrict_unauthenticated, :activities, :local])
 | 
			
		||||
 | 
			
		||||
  test "it renders all supported types of attachments and skips unknown types" do
 | 
			
		||||
    user = insert(:user)
 | 
			
		||||
| 
						 | 
				
			
			@ -188,4 +190,24 @@ test "video attachments have no image thumbnail with Preview Proxy disabled" do
 | 
			
		|||
                "http://localhost:4001/proxy/preview/LzAnlke-l5oZbNzWsrHfprX1rGw/aHR0cHM6Ly9wbGVyb21hLmdvdi9hYm91dC9qdWNoZS53ZWJt/juche.webm"
 | 
			
		||||
            ], []} in result
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  test "it does not render users if profiles are marked as restricted" do
 | 
			
		||||
    clear_config([:restrict_unauthenticated, :profiles, :local], true)
 | 
			
		||||
 | 
			
		||||
    user = insert(:user)
 | 
			
		||||
 | 
			
		||||
    result = OpenGraph.build_tags(%{user: user})
 | 
			
		||||
    assert Enum.empty?(result)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  test "it does not activities users if they are marked as restricted" do
 | 
			
		||||
    clear_config([:restrict_unauthenticated, :activities, :local], true)
 | 
			
		||||
 | 
			
		||||
    user = insert(:user)
 | 
			
		||||
    note = insert(:note, data: %{"actor" => user.ap_id})
 | 
			
		||||
 | 
			
		||||
    result = OpenGraph.build_tags(%{object: note, url: note.data["id"], user: user})
 | 
			
		||||
 | 
			
		||||
    assert {:meta, [property: "og:description", content: "Content cannot be displayed."], []} in result
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,6 +14,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do
 | 
			
		|||
  alias Pleroma.Web.Metadata.Utils
 | 
			
		||||
 | 
			
		||||
  setup do: clear_config([Pleroma.Web.Metadata, :unfurl_nsfw])
 | 
			
		||||
  setup do: clear_config([:restrict_unauthenticated, :profiles, :local])
 | 
			
		||||
  setup do: clear_config([:restrict_unauthenticated, :activities, :local])
 | 
			
		||||
 | 
			
		||||
  test "it renders twitter card for user info" do
 | 
			
		||||
    user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +30,14 @@ test "it renders twitter card for user info" do
 | 
			
		|||
           ]
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  test "it does not render twitter card for user info if it is restricted" do
 | 
			
		||||
    clear_config([:restrict_unauthenticated, :profiles, :local], true)
 | 
			
		||||
    user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
 | 
			
		||||
    res = TwitterCard.build_tags(%{user: user})
 | 
			
		||||
 | 
			
		||||
    assert Enum.empty?(res)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  test "it uses summary twittercard if post has no attachment" do
 | 
			
		||||
    user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
 | 
			
		||||
    {:ok, activity} = CommonAPI.post(user, %{status: "HI"})
 | 
			
		||||
| 
						 | 
				
			
			@ -54,6 +64,16 @@ test "it uses summary twittercard if post has no attachment" do
 | 
			
		|||
           ] == result
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  test "it does not summarise activities if they are marked as restricted" do
 | 
			
		||||
    clear_config([:restrict_unauthenticated, :activities, :local], true)
 | 
			
		||||
    user = insert(:user)
 | 
			
		||||
    note = insert(:note, data: %{"actor" => user.ap_id})
 | 
			
		||||
 | 
			
		||||
    result = TwitterCard.build_tags(%{object: note, activity_id: note.data["id"], user: user})
 | 
			
		||||
 | 
			
		||||
    assert {:meta, [name: "twitter:description", content: "Content cannot be displayed."], []} in result
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  test "it uses summary as description if post has one" do
 | 
			
		||||
    user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
 | 
			
		||||
    {:ok, activity} = CommonAPI.post(user, %{status: "HI"})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue