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:
Floatingghost 2024-04-12 05:16:47 +01:00
parent 1135935cbe
commit 05f8179d08
6 changed files with 115 additions and 25 deletions

View file

@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Fixed ## Fixed
- Issue preventing fetching anything from IPv6-only instances - Issue preventing fetching anything from IPv6-only instances
- Issue allowing post content to leak via opengraph tags despite :estrict\_unauthenticated being set
## 2024.03 ## 2024.03

View file

@ -12,14 +12,38 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
@behaviour Provider @behaviour Provider
@media_types ["image", "audio", "video"] @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 @impl Provider
def build_tags(%{ def build_tags(%{
object: object, object: object,
url: url, url: url,
user: user user: user
}) do }) do
attachments = build_attachments(object) attachments =
scrubbed_content = Utils.scrub_html_and_truncate(object) 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, {:meta,
@ -36,12 +60,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
{:meta, [property: "og:type", content: "article"], []} {:meta, [property: "og:type", content: "article"], []}
] ++ ] ++
if attachments == [] or Metadata.activity_nsfw?(object) do if attachments == [] or Metadata.activity_nsfw?(object) do
[ user_avatar_tags(user)
{: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 else
attachments attachments
end end
@ -49,7 +68,9 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
@impl Provider @impl Provider
def build_tags(%{user: user}) do 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, {:meta,
[ [
@ -58,12 +79,10 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
], []}, ], []},
{:meta, [property: "og:url", content: user.uri || user.ap_id], []}, {:meta, [property: "og:url", content: user.uri || user.ap_id], []},
{:meta, [property: "og:description", content: truncated_bio], []}, {:meta, [property: "og:description", content: truncated_bio], []},
{:meta, [property: "og:type", content: "article"], []}, {:meta, [property: "og:type", content: "article"], []}
{:meta, [property: "og:image", content: MediaProxy.preview_url(User.avatar_url(user))], ] ++ user_avatar_tags(user)
[]}, else
{:meta, [property: "og:image:width", content: 150], []}, []
{:meta, [property: "og:image:height", content: 150], []}
]
end end
end end

View file

@ -17,8 +17,19 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
@impl Provider @impl Provider
def build_tags(%{activity_id: id, object: object, user: user}) do def build_tags(%{activity_id: id, object: object, user: user}) do
attachments = build_attachments(id, object) attachments =
scrubbed_content = Utils.scrub_html_and_truncate(object) 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), title_tag(user),
@ -36,13 +47,17 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
@impl Provider @impl Provider
def build_tags(%{user: user}) do def build_tags(%{user: user}) do
with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do 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], []}, title_tag(user),
image_tag(user), {:meta, [name: "twitter:description", content: truncated_bio], []},
{:meta, [name: "twitter:card", content: "summary"], []} image_tag(user),
] {:meta, [name: "twitter:card", content: "summary"], []}
]
end
else
[]
end end
end end
@ -51,7 +66,11 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
end end
def image_tag(user) do 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 end
defp build_attachments(id, %{data: %{"attachment" => attachments}}) do defp build_attachments(id, %{data: %{"attachment" => attachments}}) do

View file

@ -7,6 +7,15 @@ defmodule Pleroma.Web.Metadata.Utils do
alias Pleroma.Emoji alias Pleroma.Emoji
alias Pleroma.Formatter alias Pleroma.Formatter
alias Pleroma.HTML 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 defp scrub_html_and_truncate_object_field(field, object) do
field field

View file

@ -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.Web.Metadata, :unfurl_nsfw])
setup do: clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local) 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 test "it renders all supported types of attachments and skips unknown types" do
user = insert(:user) user = insert(:user)
@ -188,4 +190,24 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraphTest do
"http://localhost:4001/proxy/preview/LzAnlke-l5oZbNzWsrHfprX1rGw/aHR0cHM6Ly9wbGVyb21hLmdvdi9hYm91dC9qdWNoZS53ZWJt/juche.webm" "http://localhost:4001/proxy/preview/LzAnlke-l5oZbNzWsrHfprX1rGw/aHR0cHM6Ly9wbGVyb21hLmdvdi9hYm91dC9qdWNoZS53ZWJt/juche.webm"
], []} in result ], []} in result
end 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 end

View file

@ -14,6 +14,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do
alias Pleroma.Web.Metadata.Utils alias Pleroma.Web.Metadata.Utils
setup do: clear_config([Pleroma.Web.Metadata, :unfurl_nsfw]) 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 test "it renders twitter card for user info" do
user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994") user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
@ -28,6 +30,14 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do
] ]
end 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 test "it uses summary twittercard if post has no attachment" do
user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994") user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
{:ok, activity} = CommonAPI.post(user, %{status: "HI"}) {:ok, activity} = CommonAPI.post(user, %{status: "HI"})
@ -54,6 +64,16 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do
] == result ] == result
end 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 test "it uses summary as description if post has one" do
user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994") user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")
{:ok, activity} = CommonAPI.post(user, %{status: "HI"}) {:ok, activity} = CommonAPI.post(user, %{status: "HI"})