From a51284b60ab450a6c7ff644f1ea10f797a36aa59 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 29 May 2020 09:46:31 +0000 Subject: [PATCH] Merge branch 'fix/mediaproxy-bypass-emoji' into 'develop' Fix profile emojis bypassing mediaproxy and harden CSP Closes #1810 See merge request pleroma/pleroma!2596 --- lib/pleroma/plugs/http_security_plug.ex | 82 +++++++++++++++++------ test/plugs/http_security_plug_test.exs | 2 +- test/web/media_proxy/media_proxy_test.exs | 16 ++--- 3 files changed, 71 insertions(+), 29 deletions(-) diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex index 6462797b6..2208d1d6c 100644 --- a/lib/pleroma/plugs/http_security_plug.ex +++ b/lib/pleroma/plugs/http_security_plug.ex @@ -31,7 +31,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do {"x-content-type-options", "nosniff"}, {"referrer-policy", referrer_policy}, {"x-download-options", "noopen"}, - {"content-security-policy", csp_string() <> ";"} + {"content-security-policy", csp_string()} ] if report_uri do @@ -43,23 +43,46 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do ] } - headers ++ [{"reply-to", Jason.encode!(report_group)}] + [{"reply-to", Jason.encode!(report_group)} | headers] else headers end end + static_csp_rules = [ + "default-src 'none'", + "base-uri 'self'", + "frame-ancestors 'none'", + "style-src 'self' 'unsafe-inline'", + "font-src 'self'", + "manifest-src 'self'" + ] + + @csp_start [Enum.join(static_csp_rules, ";") <> ";"] + defp csp_string do scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme] static_url = Pleroma.Web.Endpoint.static_url() websocket_url = Pleroma.Web.Endpoint.websocket_url() report_uri = Config.get([:http_security, :report_uri]) - connect_src = "connect-src 'self' #{static_url} #{websocket_url}" + img_src = "img-src 'self' data: blob:" + media_src = "media-src 'self'" + + {img_src, media_src} = + if Config.get([:media_proxy, :enabled]) && + !Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do + sources = get_proxy_and_attachment_sources() + {[img_src, sources], [media_src, sources]} + else + {img_src, media_src} + end + + connect_src = ["connect-src 'self' ", static_url, ?\s, websocket_url] connect_src = if Pleroma.Config.get(:env) == :dev do - connect_src <> " http://localhost:3035/" + [connect_src, " http://localhost:3035/"] else connect_src end @@ -71,27 +94,46 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do "script-src 'self'" end - main_part = [ - "default-src 'none'", - "base-uri 'self'", - "frame-ancestors 'none'", - "img-src 'self' data: blob: https:", - "media-src 'self' https:", - "style-src 'self' 'unsafe-inline'", - "font-src 'self'", - "manifest-src 'self'", - connect_src, - script_src - ] + report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"] + insecure = if scheme == "https", do: "upgrade-insecure-requests" + + @csp_start + |> add_csp_param(img_src) + |> add_csp_param(media_src) + |> add_csp_param(connect_src) + |> add_csp_param(script_src) + |> add_csp_param(insecure) + |> add_csp_param(report) + |> :erlang.iolist_to_binary() + end + + defp get_proxy_and_attachment_sources do + media_proxy_whitelist = + Enum.reduce(Config.get([:media_proxy, :whitelist]), [], fn host, acc -> + add_source(acc, host) + end) - report = if report_uri, do: ["report-uri #{report_uri}; report-to csp-endpoint"], else: [] + upload_base_url = + if Config.get([Pleroma.Upload, :base_url]), + do: URI.parse(Config.get([Pleroma.Upload, :base_url])).host - insecure = if scheme == "https", do: ["upgrade-insecure-requests"], else: [] + s3_endpoint = + if Config.get([Pleroma.Upload, :uploader]) == Pleroma.Uploaders.S3, + do: URI.parse(Config.get([Pleroma.Uploaders.S3, :public_endpoint])).host - (main_part ++ report ++ insecure) - |> Enum.join("; ") + [] + |> add_source(upload_base_url) + |> add_source(s3_endpoint) + |> add_source(media_proxy_whitelist) end + defp add_source(iodata, nil), do: iodata + defp add_source(iodata, source), do: [[?\s, source] | iodata] + + defp add_csp_param(csp_iodata, nil), do: csp_iodata + + defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata] + def warn_if_disabled do unless Config.get([:http_security, :enabled]) do Logger.warn(" diff --git a/test/plugs/http_security_plug_test.exs b/test/plugs/http_security_plug_test.exs index 944a9a139..6ba2dfe85 100644 --- a/test/plugs/http_security_plug_test.exs +++ b/test/plugs/http_security_plug_test.exs @@ -67,7 +67,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do [csp] = Conn.get_resp_header(conn, "content-security-policy") - assert csp =~ ~r|report-uri https://endpoint.com; report-to csp-endpoint;| + assert csp =~ ~r|report-uri https://endpoint.com;report-to csp-endpoint;| [reply_to] = Conn.get_resp_header(conn, "reply-to") diff --git a/test/web/media_proxy/media_proxy_test.exs b/test/web/media_proxy/media_proxy_test.exs index 8f5fcf2eb..dc4388f58 100644 --- a/test/web/media_proxy/media_proxy_test.exs +++ b/test/web/media_proxy/media_proxy_test.exs @@ -125,15 +125,8 @@ defmodule Pleroma.Web.MediaProxyTest do test "uses the configured base_url" do base_url = Pleroma.Config.get([:media_proxy, :base_url]) - - if base_url do - on_exit(fn -> - Pleroma.Config.put([:media_proxy, :base_url], base_url) - end) - end - Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social") - + on_exit(fn -> Pleroma.Config.put([:media_proxy, :base_url], base_url) end) url = "https://pleroma.soykaf.com/static/logo.png" encoded = url(url) @@ -213,9 +206,16 @@ defmodule Pleroma.Web.MediaProxyTest do end test "does not change whitelisted urls" do + whitelist = Pleroma.Config.get([:media_proxy, :whitelist]) + base_url = Pleroma.Config.get([:media_proxy, :base_url]) Pleroma.Config.put([:media_proxy, :whitelist], ["mycdn.akamai.com"]) Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social") + on_exit(fn -> + Pleroma.Config.put([:media_proxy, :whitelist], whitelist) + Pleroma.Config.put([:media_proxy, :base_url], base_url) + end) + media_url = "https://mycdn.akamai.com" url = "#{media_url}/static/logo.png" -- 2.45.2