fix(html): check link schemes the way browsers read them
FastSanitize checks the scheme with URI.parse/1, which finds none in java<TAB>script:alert(1). Browsers remove tabs and newlines and leading control characters first and run it as a javascript: link. The scrubbers now use Pleroma.HTML.URIAttributes, which normalizes the URL the same way before the scheme check.
This commit is contained in:
parent
58d4f6aa2c
commit
1be50ed28b
6 changed files with 95 additions and 5 deletions
1
changelog.d/scrubber-hidden-schemes.security
Normal file
1
changelog.d/scrubber-hidden-schemes.security
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Scrubbers: remove links and images whose scheme is hidden with tabs, newlines or leading control characters (for example java<TAB>script:), which browsers still run as javascript:. Custom scrubbers should use Pleroma.HTML.URIAttributes.allow_tag_with_uri_attributes instead of the FastSanitize macro
|
||||||
40
lib/pleroma/html/uri_attributes.ex
Normal file
40
lib/pleroma/html/uri_attributes.ex
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.HTML.URIAttributes do
|
||||||
|
@moduledoc """
|
||||||
|
URI attribute rules for the scrubbers.
|
||||||
|
|
||||||
|
Works like `FastSanitize.Sanitizer.Meta.allow_tag_with_uri_attributes/3`, but
|
||||||
|
checks the scheme the way a browser reads the URL. Browsers remove tabs and
|
||||||
|
newlines anywhere and control characters and spaces at the start, so
|
||||||
|
`java\\tscript:` is a `javascript:` link to them, while `URI.parse/1` finds no
|
||||||
|
scheme and FastSanitize lets it through.
|
||||||
|
"""
|
||||||
|
|
||||||
|
defmacro allow_tag_with_uri_attributes(tag, attrs, valid_schemes) do
|
||||||
|
for attr <- attrs do
|
||||||
|
quote do
|
||||||
|
def scrub_attribute(unquote(tag), {unquote(attr), "&" <> _}), do: nil
|
||||||
|
|
||||||
|
def scrub_attribute(unquote(tag), {unquote(attr), uri} = attribute) do
|
||||||
|
if Pleroma.HTML.URIAttributes.valid_scheme?(uri, unquote(valid_schemes)),
|
||||||
|
do: attribute
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec valid_scheme?(String.t(), [String.t()]) :: boolean()
|
||||||
|
def valid_scheme?(uri, valid_schemes) do
|
||||||
|
scheme =
|
||||||
|
uri
|
||||||
|
|> String.replace(~r/[\t\n\r]/, "")
|
||||||
|
|> String.replace(~r/\A[\x00-\x20]+/, "")
|
||||||
|
|> URI.parse()
|
||||||
|
|> Map.get(:scheme)
|
||||||
|
|
||||||
|
is_nil(scheme) or scheme in valid_schemes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
||||||
@doc "The default HTML scrubbing policy: no "
|
@doc "The default HTML scrubbing policy: no "
|
||||||
|
|
||||||
require FastSanitize.Sanitizer.Meta
|
require FastSanitize.Sanitizer.Meta
|
||||||
|
require Pleroma.HTML.URIAttributes
|
||||||
alias FastSanitize.Sanitizer.Meta
|
alias FastSanitize.Sanitizer.Meta
|
||||||
|
|
||||||
# credo:disable-for-previous-line
|
# credo:disable-for-previous-line
|
||||||
|
|
@ -15,7 +16,11 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
||||||
|
|
||||||
Meta.strip_comments()
|
Meta.strip_comments()
|
||||||
|
|
||||||
Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
|
Pleroma.HTML.URIAttributes.allow_tag_with_uri_attributes(
|
||||||
|
:a,
|
||||||
|
["href", "data-user", "data-tag"],
|
||||||
|
@valid_schemes
|
||||||
|
)
|
||||||
|
|
||||||
Meta.allow_tag_with_this_attribute_values(:a, "class", [
|
Meta.allow_tag_with_this_attribute_values(:a, "class", [
|
||||||
"hashtag",
|
"hashtag",
|
||||||
|
|
@ -145,7 +150,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
||||||
Meta.allow_tag_with_this_attribute_values(:img, "class", ["emoji"])
|
Meta.allow_tag_with_this_attribute_values(:img, "class", ["emoji"])
|
||||||
|
|
||||||
# restrict img tags to http/https only, because of MediaProxy.
|
# restrict img tags to http/https only, because of MediaProxy.
|
||||||
Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
|
Pleroma.HTML.URIAttributes.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
|
||||||
|
|
||||||
Meta.allow_tag_with_these_attributes(:img, [
|
Meta.allow_tag_with_these_attributes(:img, [
|
||||||
"width",
|
"width",
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,13 @@ defmodule Pleroma.HTML.Scrubber.LinksOnly do
|
||||||
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
|
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
|
||||||
|
|
||||||
require FastSanitize.Sanitizer.Meta
|
require FastSanitize.Sanitizer.Meta
|
||||||
|
require Pleroma.HTML.URIAttributes
|
||||||
alias FastSanitize.Sanitizer.Meta
|
alias FastSanitize.Sanitizer.Meta
|
||||||
|
|
||||||
Meta.strip_comments()
|
Meta.strip_comments()
|
||||||
|
|
||||||
# links
|
# links
|
||||||
Meta.allow_tag_with_uri_attributes(:a, ["href"], @valid_schemes)
|
Pleroma.HTML.URIAttributes.allow_tag_with_uri_attributes(:a, ["href"], @valid_schemes)
|
||||||
|
|
||||||
Meta.allow_tag_with_this_attribute_values(:a, "rel", [
|
Meta.allow_tag_with_this_attribute_values(:a, "rel", [
|
||||||
"tag",
|
"tag",
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,17 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
|
||||||
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
|
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
|
||||||
|
|
||||||
require FastSanitize.Sanitizer.Meta
|
require FastSanitize.Sanitizer.Meta
|
||||||
|
require Pleroma.HTML.URIAttributes
|
||||||
alias FastSanitize.Sanitizer.Meta
|
alias FastSanitize.Sanitizer.Meta
|
||||||
|
|
||||||
Meta.strip_comments()
|
Meta.strip_comments()
|
||||||
|
|
||||||
# links
|
# links
|
||||||
Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes)
|
Pleroma.HTML.URIAttributes.allow_tag_with_uri_attributes(
|
||||||
|
:a,
|
||||||
|
["href", "data-user", "data-tag"],
|
||||||
|
@valid_schemes
|
||||||
|
)
|
||||||
|
|
||||||
Meta.allow_tag_with_this_attribute_values(:a, "class", [
|
Meta.allow_tag_with_this_attribute_values(:a, "class", [
|
||||||
"hashtag",
|
"hashtag",
|
||||||
|
|
@ -49,7 +54,7 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
|
||||||
Meta.allow_tag_with_this_attribute_values(:img, "class", ["emoji"])
|
Meta.allow_tag_with_this_attribute_values(:img, "class", ["emoji"])
|
||||||
|
|
||||||
# restrict img tags to http/https only, because of MediaProxy.
|
# restrict img tags to http/https only, because of MediaProxy.
|
||||||
Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
|
Pleroma.HTML.URIAttributes.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"])
|
||||||
|
|
||||||
Meta.allow_tag_with_these_attributes(:img, [
|
Meta.allow_tag_with_these_attributes(:img, [
|
||||||
"width",
|
"width",
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,44 @@ defmodule Pleroma.HTMLTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "link schemes as a browser sees them" do
|
||||||
|
# Browsers remove tabs and newlines in URLs and control characters and
|
||||||
|
# spaces at the start, so these are javascript: links to them.
|
||||||
|
test "removes javascript: links hidden with control characters" do
|
||||||
|
for href <- [
|
||||||
|
"java\tscript:alert(1)",
|
||||||
|
"java	script:alert(1)",
|
||||||
|
"java\nscript:alert(1)",
|
||||||
|
"
javascript:alert(1)",
|
||||||
|
" javascript:alert(1)",
|
||||||
|
"\u0001javascript:alert(1)"
|
||||||
|
],
|
||||||
|
scrubber <- [
|
||||||
|
Pleroma.HTML.Scrubber.Default,
|
||||||
|
Pleroma.HTML.Scrubber.TwitterText,
|
||||||
|
Pleroma.HTML.Scrubber.LinksOnly
|
||||||
|
] do
|
||||||
|
html = HTML.filter_tags(~s(<a href="#{href}">x</a>), scrubber)
|
||||||
|
|
||||||
|
refute html =~ "href", "#{inspect(href)} survived #{inspect(scrubber)}: #{html}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "keeps normal links" do
|
||||||
|
for href <- ["https://example.org/a?b=c", "/relative/path", "#fragment"] do
|
||||||
|
assert HTML.filter_tags(~s(<a href="#{href}">x</a>), Pleroma.HTML.Scrubber.Default) ==
|
||||||
|
~s(<a href="#{href}">x</a>)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "removes images with hidden schemes" do
|
||||||
|
html =
|
||||||
|
HTML.filter_tags(~s|<img src="java\tscript:alert(1)"/>|, Pleroma.HTML.Scrubber.Default)
|
||||||
|
|
||||||
|
refute html =~ "src"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "default scrubber" do
|
describe "default scrubber" do
|
||||||
test "normalizes HTML as expected" do
|
test "normalizes HTML as expected" do
|
||||||
expected = """
|
expected = """
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue