logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma

http_security_plug.ex (7306B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Plugs.HTTPSecurityPlug do
  5. alias Pleroma.Config
  6. import Plug.Conn
  7. require Logger
  8. def init(opts), do: opts
  9. def call(conn, _options) do
  10. if Config.get([:http_security, :enabled]) do
  11. conn
  12. |> merge_resp_headers(headers())
  13. |> maybe_send_sts_header(Config.get([:http_security, :sts]))
  14. else
  15. conn
  16. end
  17. end
  18. defp headers do
  19. referrer_policy = Config.get([:http_security, :referrer_policy])
  20. report_uri = Config.get([:http_security, :report_uri])
  21. headers = [
  22. {"x-xss-protection", "1; mode=block"},
  23. {"x-permitted-cross-domain-policies", "none"},
  24. {"x-frame-options", "DENY"},
  25. {"x-content-type-options", "nosniff"},
  26. {"referrer-policy", referrer_policy},
  27. {"x-download-options", "noopen"},
  28. {"content-security-policy", csp_string()}
  29. ]
  30. if report_uri do
  31. report_group = %{
  32. "group" => "csp-endpoint",
  33. "max-age" => 10_886_400,
  34. "endpoints" => [
  35. %{"url" => report_uri}
  36. ]
  37. }
  38. [{"reply-to", Jason.encode!(report_group)} | headers]
  39. else
  40. headers
  41. end
  42. end
  43. static_csp_rules = [
  44. "default-src 'none'",
  45. "base-uri 'self'",
  46. "frame-ancestors 'none'",
  47. "style-src 'self' 'unsafe-inline'",
  48. "font-src 'self'",
  49. "manifest-src 'self'"
  50. ]
  51. @csp_start [Enum.join(static_csp_rules, ";") <> ";"]
  52. defp csp_string do
  53. scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
  54. static_url = Pleroma.Web.Endpoint.static_url()
  55. websocket_url = Pleroma.Web.Endpoint.websocket_url()
  56. report_uri = Config.get([:http_security, :report_uri])
  57. img_src = "img-src 'self' data: blob:"
  58. media_src = "media-src 'self'"
  59. # Strict multimedia CSP enforcement only when MediaProxy is enabled
  60. {img_src, media_src} =
  61. if Config.get([:media_proxy, :enabled]) &&
  62. !Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do
  63. sources = build_csp_multimedia_source_list()
  64. {[img_src, sources], [media_src, sources]}
  65. else
  66. {[img_src, " https:"], [media_src, " https:"]}
  67. end
  68. connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url]
  69. connect_src =
  70. if Config.get(:env) == :dev do
  71. [connect_src, " http://localhost:3035/"]
  72. else
  73. connect_src
  74. end
  75. script_src =
  76. if Config.get(:env) == :dev do
  77. "script-src 'self' 'unsafe-eval'"
  78. else
  79. "script-src 'self'"
  80. end
  81. report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"]
  82. insecure = if scheme == "https", do: "upgrade-insecure-requests"
  83. @csp_start
  84. |> add_csp_param(img_src)
  85. |> add_csp_param(media_src)
  86. |> add_csp_param(connect_src)
  87. |> add_csp_param(script_src)
  88. |> add_csp_param(insecure)
  89. |> add_csp_param(report)
  90. |> :erlang.iolist_to_binary()
  91. end
  92. defp build_csp_from_whitelist([], acc), do: acc
  93. defp build_csp_from_whitelist([last], acc) do
  94. [build_csp_param_from_whitelist(last) | acc]
  95. end
  96. defp build_csp_from_whitelist([head | tail], acc) do
  97. build_csp_from_whitelist(tail, [[?\s, build_csp_param_from_whitelist(head)] | acc])
  98. end
  99. # TODO: use `build_csp_param/1` after removing support bare domains for media proxy whitelist
  100. defp build_csp_param_from_whitelist("http" <> _ = url) do
  101. build_csp_param(url)
  102. end
  103. defp build_csp_param_from_whitelist(url), do: url
  104. defp build_csp_multimedia_source_list do
  105. media_proxy_whitelist =
  106. [:media_proxy, :whitelist]
  107. |> Config.get()
  108. |> build_csp_from_whitelist([])
  109. captcha_method = Config.get([Pleroma.Captcha, :method])
  110. captcha_endpoint = Config.get([captcha_method, :endpoint])
  111. base_endpoints =
  112. [
  113. [:media_proxy, :base_url],
  114. [Pleroma.Upload, :base_url],
  115. [Pleroma.Uploaders.S3, :public_endpoint]
  116. ]
  117. |> Enum.map(&Config.get/1)
  118. [captcha_endpoint | base_endpoints]
  119. |> Enum.map(&build_csp_param/1)
  120. |> Enum.reduce([], &add_source(&2, &1))
  121. |> add_source(media_proxy_whitelist)
  122. end
  123. defp add_source(iodata, nil), do: iodata
  124. defp add_source(iodata, []), do: iodata
  125. defp add_source(iodata, source), do: [[?\s, source] | iodata]
  126. defp add_csp_param(csp_iodata, nil), do: csp_iodata
  127. defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
  128. defp build_csp_param(nil), do: nil
  129. defp build_csp_param(url) when is_binary(url) do
  130. %{host: host, scheme: scheme} = URI.parse(url)
  131. if scheme do
  132. [scheme, "://", host]
  133. end
  134. end
  135. def warn_if_disabled do
  136. unless Config.get([:http_security, :enabled]) do
  137. Logger.warn("
  138. .i;;;;i.
  139. iYcviii;vXY:
  140. .YXi .i1c.
  141. .YC. . in7.
  142. .vc. ...... ;1c.
  143. i7, .. .;1;
  144. i7, .. ... .Y1i
  145. ,7v .6MMM@; .YX,
  146. .7;. ..IMMMMMM1 :t7.
  147. .;Y. ;$MMMMMM9. :tc.
  148. vY. .. .nMMM@MMU. ;1v.
  149. i7i ... .#MM@M@C. .....:71i
  150. it: .... $MMM@9;.,i;;;i,;tti
  151. :t7. ..... 0MMMWv.,iii:::,,;St.
  152. .nC. ..... IMMMQ..,::::::,.,czX.
  153. .ct: ....... .ZMMMI..,:::::::,,:76Y.
  154. c2: ......,i..Y$M@t..:::::::,,..inZY
  155. vov ......:ii..c$MBc..,,,,,,,,,,..iI9i
  156. i9Y ......iii:..7@MA,..,,,,,,,,,....;AA:
  157. iIS. ......:ii::..;@MI....,............;Ez.
  158. .I9. ......:i::::...8M1..................C0z.
  159. .z9; ......:i::::,.. .i:...................zWX.
  160. vbv ......,i::::,,. ................. :AQY
  161. c6Y. .,...,::::,,..:t0@@QY. ................ :8bi
  162. :6S. ..,,...,:::,,,..EMMMMMMI. ............... .;bZ,
  163. :6o, .,,,,..:::,,,..i#MMMMMM#v................. YW2.
  164. .n8i ..,,,,,,,::,,,,.. tMMMMM@C:.................. .1Wn
  165. 7Uc. .:::,,,,,::,,,,.. i1t;,..................... .UEi
  166. 7C...::::::::::::,,,,.. .................... vSi.
  167. ;1;...,,::::::,......... .................. Yz:
  168. v97,......... .voC.
  169. izAotX7777777777777777777777777777777777777777Y7n92:
  170. .;CoIIIIIUAA666666699999ZZZZZZZZZZZZZZZZZZZZ6ov.
  171. HTTP Security is disabled. Please re-enable it to prevent users from attacking
  172. your instance and your users via malicious posts:
  173. config :pleroma, :http_security, enabled: true
  174. ")
  175. end
  176. end
  177. defp maybe_send_sts_header(conn, true) do
  178. max_age_sts = Config.get([:http_security, :sts_max_age])
  179. max_age_ct = Config.get([:http_security, :ct_max_age])
  180. merge_resp_headers(conn, [
  181. {"strict-transport-security", "max-age=#{max_age_sts}; includeSubDomains"},
  182. {"expect-ct", "enforce, max-age=#{max_age_ct}"}
  183. ])
  184. end
  185. defp maybe_send_sts_header(conn, _), do: conn
  186. end