本指南介绍如何使用内容安全策略(CSP)来缓解 Discourse 中的跨站脚本(XSS)攻击。内容涵盖 CSP 基础、配置方法及最佳实践。
所需用户级别:管理员
摘要
内容安全策略(CSP)是 Discourse 中一项关键的安全功能,有助于防范跨站脚本(XSS)及其他注入攻击。本指南将介绍 CSP 的基础知识、其在 Discourse 中的实现方式,以及如何为您的站点进行配置。
什么是内容安全策略?
内容安全策略是一种额外的安全层,有助于检测并缓解某些类型的攻击,包括跨站脚本(XSS)和数据注入攻击。CSP 通过指定哪些内容来源被视为可信,并指示浏览器仅执行或渲染来自这些可信来源的资源来发挥作用。
XSS 仍然是最常见的 Web 漏洞之一。通过实施 CSP,Discourse 仅允许来自可信来源的脚本加载和执行,从而显著降低 XSS 攻击的风险。
Discourse 的 CSP 实现
从 Discourse 3.3.0.beta1 版本开始,Discourse 实现了“严格动态(strict-dynamic)”CSP。该方法在 script-src 指令中使用单个 nonce- 值和 strict-dynamic 关键字。核心和主题中的所有初始 <script> 标签都会自动添加适当的 nonce= 属性。
默认策略包含以下指令:
script-src:指定 JavaScript 的有效来源
worker-src:指定 ServiceWorker 脚本的有效来源
object-src:阻止插件(Flash、Java 等)的执行
base-uri:限制 <base> 元素的 URL
manifest-src:限制 Web 应用清单的 URL
frame-ancestors:控制哪些站点可以将您的 Discourse 实例嵌入到 iframe 中
upgrade-insecure-requests:自动将 HTTP 请求升级为 HTTPS(在启用 force_https 时包含)
在 Discourse 中配置 CSP
可用设置
content_security_policy:启用或禁用 CSP(默认:开启)
content_security_policy_report_only:启用 CSP 报告模式(默认:关闭)
content_security_policy_script_src:允许您扩展默认的 script-src 指令
content_security_policy_frame_ancestors:启用 frame_ancestors 指令 (默认:开启)
如何启用 CSP
导航至您的管理面板
进入安全设置
找到 content_security_policy 设置并确保其已启用
建议先启用 CSP 报告模式以识别任何潜在问题,然后再完全启用 CSP:
启用 content_security_policy_report_only 设置
监控浏览器控制台中的 CSP 违规报告
根据需要扩展 CSP 以解决任何合法的违规
一旦确认没有误报,请禁用报告模式并完全启用 CSP
扩展默认 CSP
如果您需要允许额外的脚本来源,可以使用 content_security_policy_script_src 设置扩展 script-src 指令。您可以添加:
哈希来源
'wasm-unsafe-eval'
'unsafe-eval'(请谨慎使用)
例如:
'sha256-QFlnYO2Ll+rgFRKkUmtyRublBc7KFNsbzF7BzoCqjgA=' 'unsafe-eval'
添加 'unsafe-eval' 或其他宽松指令时请务必谨慎,因为它们可能会降低 CSP 的有效性。
CSP 与第三方集成
当使用 Google Tag Manager、Google Analytics 或广告服务等第三方服务时,您可能需要调整 CSP 设置。在大多数情况下,使用 Discourse 3.3.0.beta1 或更高版本时,由于实施了“严格动态”CSP,外部脚本无需额外配置即可正常工作。
如果您遇到问题,可能需要:
通过监控浏览器控制台识别所需的脚本来源
将必要的来源添加到 content_security_policy_script_src 设置中
对于像广告服务这样加载外部资源的复杂集成,您可能需要启用跨域渲染(参考 discourse-adplugin 的相关 PR 示例)。
最佳实践
从 CSP 报告模式开始,以识别潜在问题
在解决合法违规后,逐步收紧 CSP
定期审查 CSP 设置并根据需要进行调整
添加 'unsafe-eval' 或 'wasm-unsafe-eval' 等宽松指令时请谨慎
保持 Discourse 实例更新,以利用最新的 CSP 改进
常见问题解答
问:我看到了很多 CSP 违规报告。我应该担心吗?
答:许多 CSP 违规是误报,通常由浏览器扩展或其他无关脚本引起。请重点关注与站点功能相关的违规。
问:我可以在 CSP 中使用 Google AdSense 或其他广告网络吗?
答:可以,但您可能需要使用更宽松的 CSP 设置。建议从报告模式开始,并根据报告的违规情况调整设置。
问:如何排查 CSP 问题?
答:使用浏览器的开发者工具监控控制台中的 CSP 违规消息。这将帮助您识别哪些资源被阻止以及原因。
其他资源
56 个赞
I added a note about this to our public security.md file
13 个赞
pmusaraj
(Penar Musaraj)
2019 年1 月 15 日 16:36
3
As of this commit , we’ve turned off CSP violations reports by default because the vast majority of the reported violations are false positives.
To illustrate this, here is a screenshot of logs from a site running Discourse with CSP enabled and reporting enabled (filtered using “CSP Violation”):
All of the reported violations are not related to the site’s code:
violations with ‘minisrclink.cool’ or ‘proxdev.cool’ in the URL have nothing to do with Discourse, they’re likely coming from a browser extension
the Google Analytics violation reports are also not legitimate. They are triggered by Firefox in privacy mode, or Firefox with a privacy extension enabled (like DuckDuckGo Privacy Essentials).
Violations with ‘inline’, ‘data’ or ‘about’ are triggered by extensions as well. It’s not shown in the screenshot above, but these violations have some more details in the env tab of the log. In there, under script-sample, some of these violations had code like BlockAdBlock or window.klTabId_kis or AG_onLoad, which come from the AdBlock, Kaspersky, and AdGuard extensions, respectively. (I found this repo: CSP-useful/csp-wtf/README.md at master · nico3333fr/CSP-useful · GitHub very useful in helping explain some of these reports.) Some of these violations will have safari-extension or user-script in the source-file variable (again, in env), so that points to Safari extensions as the culprit for the violation.
In other words, there’s a lot of noise in CSP violation reports, so it’s not useful to log them at all times. They might be helpful while you are configuring CSP, but the reporting should be off during the normal operation of a site.
A few final notes: if you site is using a tag manager (like Google Tag Manager or Segment) you need to load the site in your browser, and carefully examine the violations in the console. These tools load third-party scripts from third-party domains and/or inline scripts so you need to carefully whitelist each of them using the source URL or the hash of the inline script (Chrome usefully includes the hash of inline scripts in the console error statement).
If your site uses an advertising service (like Google Ad Manager, Adsense, etc.) you probably will have to use a very permissive policy:
In the screenshot above, the policy allows any script from a https: source and any inline script. (In the future, this might be replaced by the strict-dynamic keyword, but as of this writing, strict-dynamic isn’t supported by Safari or Edge.)
24 个赞
Please note that Safari doesn’t understand some parts of CSP, and this is normal:
You can safely ignore the CSP errors in Safari, you’ll see those on all sites, it just means Safari doesn’t understand worker-src and report-sample .
I guess we need to wait for Safari to be updated?
12 个赞
pmusaraj
(Penar Musaraj)
2020 年1 月 29 日 20:54
22
这取决于您的广告请求的 URL。您可以查看浏览器的控制台来查看它们。
另请参阅原始帖子的相关部分:
xrav3nz:
4. 第三方脚本/服务集成
不同的集成有不同的要求,但解决方式类似。
您可以查找该集成推荐的 CSP 白名单,并相应地扩展默认 CSP。我建议简单地 在 Discourse 中开启 CSP 报告模式 ,然后观察您的控制台,以确定需要白名单哪些资源才能使您的集成正常工作。
在使用像 Google Tag Manager 或 Segment 这样的第三方脚本捆绑器时,这一点尤为重要,因为这些捆绑器可能会加载许多第三方或内联脚本。(即使在使用 Segment 或 GTM 时,您最终可能不得不添加 'unsafe-inline',尽管应尽可能避免这种定义。)
4 个赞
您能否添加一个 Feature-Policy ?
这是我使用了一年多的配置。(主机为 nginx)
add_header Feature-Policy “geolocation ‘none’; midi ‘none’; notifications ‘self’; push ‘none’; sync-xhr ‘none’; microphone ‘none’; camera ‘none’; magnetometer ‘none’; gyroscope ‘none’; speaker ‘none’; vibrate ‘none’; fullscreen ‘none’; payment ‘none’;”;
将以下内容添加到 Content-Security-Policy 头中是否有意义?这是我目前成功使用的配置(由主机 nginx 添加,在 Discourse 内置的 CSP 之上):
default-src 'none'
style-src 'self' domain 'unsafe-inline'
img-src https://*.domain.org data: blob: 'unsafe-inline'
font-src 'self' domain
connect-src 'self' domain
manifest-src 'self' domain
3 个赞
jomaxro
(Joshua Rosenfeld)
2020 年6 月 2 日 16:13
25
鉴于 该规范仍处于草案阶段 ,我们目前不打算实施它。您列出的 Mozilla 网站甚至指出:
Feature-Policy 标头仍处于实验状态,且随时可能发生变化。在您的网站上实施 Feature Policy 时,请务必注意这一点。
8 个赞
riking
(Kane York)
2020 年6 月 2 日 16:20
26
adrelanos:
vibrate ‘none’;
vibrate 'self' - Android 上的点赞会触发轻微短暂的震动。
10 个赞
R_X
2020 年8 月 13 日 08:15
29
我刚安装了 Discourse 2.6.0.beta1。需要重新配置吗?谢谢。
1 个赞
jomaxro
(Joshua Rosenfeld)
2020 年8 月 13 日 14:58
30
您不需要这样做,此功能默认已启用。只有当您发现外部资源被阻止并希望允许其运行时,才需要修改配置。
4 个赞
我目前运行的是 2.6.0 beta 2 版本。
我在论坛中使用了以下服务:
Google Tag Manager
Google Ad Manager
Google AdSense
目前,在尝试解决所有未决问题期间,我仅使用报告模式的 CSP(内容安全策略)。
以下是我的 CSP 设置:
尽管设置了当前配置,我仍然收到大量 CSP 错误。其中一些似乎可以忽略。但有一个错误让我感到困惑,因为该域名已经在 CSP 设置中声明了。
我是否遗漏了什么?
CSP 违规:'https://www.googletagmanager.com/gtm.js?id=GTM-T9ZW6PR'
回溯信息:
/var/www/discourse/app/controllers/csp_reports_controller.rb:9:in `create'
actionpack-6.0.3.2/lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'
actionpack-6.0.3.2/lib/abstract_controller/base.rb:195:in `process_action'
actionpack-6.0.3.2/lib/action_controller/metal/rendering.rb:30:in `process_action'
actionpack-6.0.3.2/lib/abstract_controller/callbacks.rb:42:in `block in process_action'
activesupport-6.0.3.2/lib/active_support/callbacks.rb:112:in `block in run_callbacks'
/var/www/discourse/app/controllers/application_controller.rb:340:in `block in with_resolved_locale'
i18n-1.8.5/lib/i18n.rb:313:in `with_locale'
/var/www/discourse/app/controllers/application_controller.rb:340:in `with_resolved_locale'
activesupport-6.0.3.2/lib/active_support/callbacks.rb:121:in `block in run_callbacks'
activesupport-6.0.3.2/lib/active_support/callbacks.rb:139:in `run_callbacks'
actionpack-6.0.3.2/lib/abstract_controller/callbacks.rb:41:in `process_action'
actionpack-6.0.3.2/lib/action_controller/metal/rescue.rb:22:in `process_action'
actionpack-6.0.3.2/lib/action_controller/metal/instrumentation.rb:33:in `block in process_action'
activesupport-6.0.3.2/lib/active_support/notifications.rb:180:in `block in instrument'
activesupport-6.0.3.2/lib/active_support/notifications/instrumenter.rb:24:in `instrument'
activesupport-6.0.3.2/lib/active_support/notifications.rb:180:in `instrument'
actionpack-6.0.3.2/lib/action_controller/metal/instrumentation.rb:32:in `process_action'
actionpack-6.0.3.2/lib/action_controller/metal/params_wrapper.rb:245:in `process_action'
activerecord-6.0.3.2/lib/active_record/railties/controller_runtime.rb:27:in `process_action'
actionpack-6.0.3.2/lib/abstract_controller/base.rb:136:in `process'
actionview-6.0.3.2/lib/action_view/rendering.rb:39:in `process'
rack-mini-profiler-2.0.4/lib/mini_profiler/profiling_methods.rb:78:in `block in profile_method'
actionpack-6.0.3.2/lib/action_controller/metal.rb:190:in `dispatch'
actionpack-6.0.3.2/lib/action_controller/metal.rb:254:in `dispatch'
actionpack-6.0.3.2/lib/action_dispatch/routing/route_set.rb:50:in `dispatch'
actionpack-6.0.3.2/lib/action_dispatch/routing/route_set.rb:33:in `serve'
actionpack-6.0.3.2/lib/action_dispatch/journey/router.rb:49:in `block in serve'
actionpack-6.0.3.2/lib/action_dispatch/journey/router.rb:32:in `each'
actionpack-6.0.3.2/lib/action_dispatch/journey/router.rb:32:in `serve'
actionpack-6.0.3.2/lib/action_dispatch/routing/route_set.rb:834:in `call'
/var/www/discourse/lib/middleware/omniauth_bypass_middleware.rb:68:in `call'
rack-2.2.3/lib/rack/tempfile_reaper.rb:15:in `call'
rack-2.2.3/lib/rack/conditional_get.rb:40:in `call'
rack-2.2.3/lib/rack/head.rb:12:in `call'
/var/www/discourse/lib/content_security_policy/middleware.rb:12:in `call'
/var/www/discourse/lib/middleware/anonymous_cache.rb:336:in `call'
rack-2.2.3/lib/rack/session/abstract/id.rb:266:in `context'
rack-2.2.3/lib/rack/session/abstract/id.rb:260:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/cookies.rb:648:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/callbacks.rb:27:in `block in call'
activesupport-6.0.3.2/lib/active_support/callbacks.rb:101:in `run_callbacks'
actionpack-6.0.3.2/lib/action_dispatch/middleware/callbacks.rb:26:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/actionable_exceptions.rb:17:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/debug_exceptions.rb:32:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
logster-2.9.3/lib/logster/middleware/reporter.rb:43:in `call'
railties-6.0.3.2/lib/rails/rack/logger.rb:37:in `call_app'
railties-6.0.3.2/lib/rails/rack/logger.rb:28:in `call'
/var/www/discourse/config/initializers/100-quiet_logger.rb:19:in `call'
/var/www/discourse/config/initializers/100-silence_logger.rb:31:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/request_id.rb:27:in `call'
/var/www/discourse/lib/middleware/enforce_hostname.rb:22:in `call'
rack-2.2.3/lib/rack/method_override.rb:24:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/executor.rb:14:in `call'
rack-2.2.3/lib/rack/sendfile.rb:110:in `call'
actionpack-6.0.3.2/lib/action_dispatch/middleware/host_authorization.rb:76:in `call'
rack-mini-profiler-2.0.4/lib/mini_profiler/profiler.rb:200:in `call'
message_bus-3.3.1/lib/message_bus/rack/middleware.rb:61:in `call'
/var/www/discourse/lib/middleware/request_tracker.rb:176:in `call'
railties-6.0.3.2/lib/rails/engine.rb:527:in `call'
railties-6.0.3.2/lib/rails/railtie.rb:190:in `public_send'
railties-6.0.3.2/lib/rails/railtie.rb:190:in `method_missing'
rack-2.2.3/lib/rack/urlmap.rb:74:in `block in call'
rack-2.2.3/lib/rack/urlmap.rb:58:in `each'
rack-2.2.3/lib/rack/urlmap.rb:58:in `call'
unicorn-5.6.0/lib/unicorn/http_server.rb:632:in `process_client'
unicorn-5.6.0/lib/unicorn/http_server.rb:728:in `worker_loop'
unicorn-5.6.0/lib/unicorn/http_server.rb:548:in `spawn_missing_workers'
unicorn-5.6.0/lib/unicorn/http_server.rb:144:in `start'
unicorn-5.6.0/bin/unicorn:128:in `<top (required)>'
/var/www/discourse/vendor/bundle/ruby/2.6.0/bin/unicorn:23:in `load'
/var/www/discourse/vendor/bundle/ruby/2.6.0/bin/unicorn:23:in `<main>'
环境 1:
hostname forums-web-only
process_id 27127
application_version f2e14a3946b020ace5a368614f0da198cd17aa32
HTTP_HOST forums.paddling.com
REQUEST_URI /csp_reports
REQUEST_METHOD POST
HTTP_USER_AGENT Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0
HTTP_ACCEPT */*
HTTP_X_FORWARDED_FOR 74.76.45.218
HTTP_X_REAL_IP 74.76.45.218
time 1:44 pm
环境 2:
hostname forums-web-only
process_id 27161
application_version f2e14a3946b020ace5a368614f0da198cd17aa32
HTTP_HOST forums.paddling.com
REQUEST_URI /csp_reports
REQUEST_METHOD POST
HTTP_USER_AGENT Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0
HTTP_ACCEPT */*
HTTP_X_FORWARDED_FOR 66.58.144.146
HTTP_X_REAL_IP 66.58.144.146
time 1:39 pm
环境 3:
hostname forums-web-only
process_id 27111
application_version f2e14a3946b020ace5a368614f0da198cd17aa32
HTTP_HOST forums.paddling.com
REQUEST_URI /csp_reports
REQUEST_METHOD POST
HTTP_USER_AGENT Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
HTTP_ACCEPT */*
HTTP_REFERER https://forums.paddling.com/t/need-advice-loading-a-kayak-onto-j-rac/60058
HTTP_X_FORWARDED_FOR 174.83.24.11
HTTP_X_REAL_IP 174.83.24.11
time 12:16 pm
2 个赞
pmusaraj
(Penar Musaraj)
2020 年9 月 2 日 18:19
32
CSP 报告中存在大量误报。有关更多详细信息,请参阅我上面的回复 。
你不需要在截图中设置的大部分规则,只需 https: 和 unsafe-inline 就足够了,它们允许所有以 https 开头的脚本和所有内联脚本。尝试清理你的 CSP 源设置并启用 CSP(不带报告功能),应该就能正常工作。
8 个赞
你好,想确认一下,Content-Security-Policy: frame-ancestors ‘none’ 指令是否是原帖中提到的“将在未来更新中包含”的那一项?
是否有可能以某种方式添加该指令,还是我应该暂时等待,不必担心?我最近在进行安全加固练习,这是某个在线安全工具提出的唯一未解决项/建议。这反而让我对平台更加有信心,各位做得真棒!
5 个赞
我认为目前不必担心。
frame-ancestors 类似于 X-Frame-Options 头 ,Discourse/Rails 已经强制实施了该头。当前该头设置为 sameorigin,大致相当于 CSP 指令中的 self 选项。
依我之见,除非我们需要支持将 self 以外的特定域名加入白名单,否则现在实施 frame-ancestors 不会带来太大收益。
8 个赞
neounix
(Dark Matter)
2021 年1 月 4 日 04:57
36
我同意这一观点。
一味追逐所有可能的漏洞扫描器“问题”,可能会破坏重要功能,而风险收益比却极低。
存在许多“理论上的漏洞”,它们很少被利用,或者只能在非常特定的场景下被利用。据我所知,这类潜在漏洞从未在 Discourse 站点上被实际利用过;因此,我建议不要“修复”那些仅在“理论上”构成漏洞、且从未导致任何实质性安全事件的问题。
作为一名拥有数十年经验的网络安全专业人士,以上是我的个人看法。如果有人感兴趣,我很乐意讨论网络安全风险管理的基础知识。
5 个赞
Falco
(Falco)
2021 年3 月 23 日 00:11
37
我们刚刚实现了对 CSP frame-ancestors 指令的支持。目前该功能默认处于禁用状态,位于 content security policy frame ancestors 站点设置之后。您可以像往常一样,通过 /admin/customize/embedding 向列表中添加域名。
该指令将在下一个发布周期中默认启用。
7 个赞