Mitigate XSS Attacks with Content Security Policy

:bookmark: This guide explains how to use Content Security Policy (CSP) to mitigate Cross-Site Scripting (XSS) attacks in Discourse. It covers CSP basics, configuration, and best practices.

:person_raising_hand: Required user level: Administrator

Summary

Content Security Policy (CSP) is a crucial security feature in Discourse that helps protect against Cross-Site Scripting (XSS) and other injection attacks. This guide covers the basics of CSP, how it’s implemented in Discourse, and how to configure it for your site.

What is Content Security Policy?

Content Security Policy is an added layer of security that helps detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks. CSP works by specifying which content sources are considered trusted, and instructing the browser to only execute or render resources from those trusted sources.

XSS remains one of the most common web vulnerabilities. By implementing CSP, Discourse allows scripts only from trusted sources to load and execute, significantly reducing the risk of XSS attacks.

Discourse’s CSP implementation

As of Discourse version 3.3.0.beta1, Discourse implements a ‘strict-dynamic’ CSP. This approach uses a single nonce- value and the strict-dynamic keyword in the script-src directive. All initial <script> tags in core and themes are automatically given the appropriate nonce= attribute.

The default policy includes the following directives:

  • script-src: Specifies valid sources for JavaScripts
  • worker-src: Specifies valid sources for ServiceWorker scripts
  • object-src: Blocks the execution of plugins (Flash, Java, etc.)
  • base-uri: Restricts the URLs for <base> elements
  • manifest-src: Restricts the URLs for web app manifests
  • frame-ancestors: Controls which sites can embed your Discourse instance in an iframe
  • upgrade-insecure-requests: Automatically upgrades HTTP requests to HTTPS (included when force_https is enabled)

Configuring CSP in Discourse

Available settings

  • content_security_policy: Enables or disables CSP (default: on)
  • content_security_policy_report_only: Enables CSP Report-Only mode (default: off)
  • content_security_policy_script_src: Allows you to extend the default script-src directive
  • content_security_policy_frame_ancestors: Enables the frame_ancestors directive (default: on)

How to enable CSP

  1. Navigate to your Admin panel
  2. Go to Security settings
  3. Find the content_security_policy setting and ensure it’s enabled

It’s recommended to start with CSP Report-Only mode to identify any potential issues before fully enabling CSP:

  1. Enable the content_security_policy_report_only setting
  2. Monitor your browser console for CSP violations
  3. Address any legitimate violations by extending the CSP as needed
  4. Once you’re confident there are no false positives, disable Report-Only mode and fully enable CSP

Extending the default CSP

If you need to allow additional script sources, you can extend the script-src directive using the content_security_policy_script_src setting. You can add:

  • Hash-sources
  • 'wasm-unsafe-eval'
  • 'unsafe-eval' (use with caution)

For example:

'sha256-QFlnYO2Ll+rgFRKkUmtyRublBc7KFNsbzF7BzoCqjgA=' 'unsafe-eval'

:warning: Be cautious when adding 'unsafe-eval' or other permissive directives, as they can reduce the effectiveness of CSP.

CSP and third-party integrations

When using third-party services like Google Tag Manager, Google Analytics, or advertising services, you may need to adjust your CSP settings. In most cases with Discourse version 3.3.0.beta1 or later, external scripts should work without additional configuration due to the ‘strict-dynamic’ CSP implementation.

If you encounter issues, you may need to:

  1. Identify the required script sources by monitoring your browser console
  2. Add the necessary sources to the content_security_policy_script_src setting
  3. For complex integrations like ad services which load external resources, you might need to enable cross-domain rendering (Example PR from discourse-adplugin that does this).

Best practices

  1. Start with CSP Report-Only mode to identify potential issues
  2. Gradually tighten your CSP as you resolve legitimate violations
  3. Regularly review your CSP settings and adjust as needed
  4. Be cautious when adding permissive directives like 'unsafe-eval' or 'wasm-unsafe-eval'
  5. Keep your Discourse instance updated to benefit from the latest CSP improvements

FAQs

Q: I’m seeing many CSP violation reports. Should I be concerned?
A: Many CSP violations are false positives, often caused by browser extensions or other unrelated scripts. Focus on addressing violations related to your site’s functionality.

Q: Can I use CSP with Google AdSense or other ad networks?
A: Yes, but you may need to use more permissive CSP settings. Start with Report-Only mode and adjust your settings based on the reported violations.

Q: How do I troubleshoot CSP issues?
A: Use your browser’s developer tools to monitor the console for CSP violation messages. These will help you identify which resources are being blocked and why.

Additional resources

Last edited by @kelv 2024-10-04T06:30:07Z

Check documentPerform check on document:
56개의 좋아요
Adsense Not Working after Recent Discourse Update
Discourse 2.2.0.beta6 Release Notes
Adding statcounter code
How to install npm packages in custom themes/plugins
Adding Cookie Consent Banner
How to restart Discourse after server reboot?
How to insert something right after <head>?
How can I embed tracking JS into Discourse
How do I integrate ? cookiebot.com in meinen Forum?
"Unsafe JavaScript attempt to initiate navigation"
Confused about remotely loaded javascript content
User input validation
Custom JS script in theme component not loading
Interactive SVG using <object>?
Header content is missing due to CSP
Video Upload to YouTube and Vimeo using Theme Component
Embed HTML5 player for MP3 file
2.5.0.beta5 breaks retort plugin
Should I load third-party libraries from vendor or cdn?
Word Cloud plugin
Embed widget within text in a topic
Discourse Intercom (Advanced)
Push custom events to Google Tag Manager and Analytics
Google Tag Manager and Discourse CSP (Content Security Policy)
Cookie Consent, GDPR, and Discourse
How to pass a component setting as a value to an attribute?
DISCOURSE_CDN_URL causes content security policy violations?
IP does not redirect to domain, domain shows white page
Need help integrating code wrote on Edittext to the Discourse
Issue with Activate Account Page After Update to 3.4.0 (Blank Page)
Any approved method for adding Javascript before body close?
Add CSP sources to the plugin
Discourse 2.4.0.beta10 Release Notes
When install html script facing issue?
How to add analytics and pixel scripts avoiding Content Security Policy (XSS)
Communities with embedded Twitter Feeds
Can't get script tag to work in landing pages plugin due to content-security-policy
[DigitalOcean] hostname having "www" in A records showing blank page
How to embed Razorpay subscription button with CSP restrictions
[DigitalOcean] hostname having "www" in A records showing blank page
Why Cookie Consent Doesn't Show Up?
"Refused to load the script" when adding adsense
A strange question about google ad display in my site
Discourse 2.2.0.beta9 Release Notes
EPN Smart Links
Nginx config in Discourse Docker?
Report Only CSP Violations
Difficulties in correctly adding external JavaScript
Javascript not working in customised areas
We couldn't find the code on your site
Where to place ad script?
Iframe issue without URL
How do we fire scripts after topic HTML is rendered in DOM?
JS script is not loading
Can I add a snippet to the header?

I added a note about this to our public security.md file :tada:

13개의 좋아요

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개의 좋아요

8 posts were split to a new topic: Protocol-less CDN URLs are problematic

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개의 좋아요

설정을 구성하는 데 문제가 있는데, 어떤 권장 사항을 따르는 게 좋을까요?

제 포럼: forum.meuxbox.com.br

링크: https://meta.discourse.org/t/white-blank-advertisement/140098/10?u=eduardo_braga

2개의 좋아요

광고가 요청하는 URL에 따라 다릅니다. 브라우저 콘솔을 확인하면 해당 URL을 볼 수 있습니다.

OP의 관련 섹션도 참고하세요:

4개의 좋아요

https://meta.discourse.org/t/white-blank-advertisement/140098/3?u=eduardo_braga

이것이 오류 문제를 해결합니다

6개의 좋아요

Feature-Policy 헤더를 추가해 주시겠습니까?

아래는 제가 1년 넘게 사용해 온 설정입니다. (호스트: 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개의 좋아요

해당 명세가 아직 초안 단계에 있기 때문에, 현재 시점에서 이를 구현할 것으로 기대하지 않습니다. 언급하신 Mozilla 웹사이트에도 다음과 같이 명시되어 있습니다:

Feature-Policy 헤더는 여전히 실험 단계에 있으며, 언제든 변경될 수 있습니다. 웹사이트에 Feature Policy를 구현할 때 이 점을 주의하시기 바랍니다.

8개의 좋아요

vibrate 'self' - Android에서 좋아요를 누르면 짧고 약한 진동이 발생합니다.

10개의 좋아요

방금 discourse 2.6.0.beta1을 설치했습니다. 이 부분을 다시 설정해야 하나요? 감사합니다

1개의 좋아요

해당 설정은 기본적으로 활성화되어 있어 변경할 필요가 없습니다. 외부 리소스가 차단되고 있으며 이를 실행하고 싶을 때만 설정을 수정하면 됩니다.

4개의 좋아요

현재 2.6.0 beta 2 버전을 사용하고 있습니다.

포럼에서 다음 서비스들을 사용 중입니다:

  • Google Tag Manager
  • Google Ad Manager
  • Google Ad Sense

현재 모든 미해결 문제를 해결하기 위해 CSP 보고(report) 모드만 사용 중입니다.

제 CSP 설정은 다음과 같습니다:

현재 설정에도 불구하고 여전히 많은 수의 CSP 오류가 발생하고 있습니다. 일부는 무시해도 되는 것 같지만, CSP 설정에 도메인을 명시해 두었음에도 불구하고 아래 오류는 이해가 되지 않습니다.

무엇을 놓치고 있는 걸까요?

CSP Violation: 'https://www.googletagmanager.com/gtm.js?id=GTM-T9ZW6PR'

backtrace:

/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>'

Env 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

Env 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

Env 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개의 좋아요

CSP 리포트에는 오탐(false positive)이 매우 많습니다. 자세한 내용은 위 답변을 참고해 주세요.

스크린샷에 설정된 대부분의 규칙은 필요 없으며, https:와 unsafe-inline만 있으면 충분합니다. 이 두 설정은 https로 시작하는 모든 스크립트와 모든 인라인 스크립트를 허용합니다. CSP 소스 설정을 정리하고 CSP(리포트 없이)를 활성화해 보세요. 정상적으로 작동할 것입니다.

8개의 좋아요

안녕하세요, 궁금한 점이 있어서 여쭤봅니다. Content-Security-Policy: frame-ancestors ‘none’ 지시문은 OP가 언급했듯이 “향후 업데이트에 포함될” 항목인가요?

이 지시문을 어떤 방식으로든 추가할 수 있는지, 아니면 그냥 기다리면 되는지 궁금합니다. 방금 보안 강화 작업을 하고 있었는데, 해당 온라인 보안 도구가 제시한 미해결 항목/권장 사항이 이것뿐이었습니다. 실제로 플랫폼에 대한 신뢰도가 많이 높아졌네요. 좋은 일 하셨습니다!

5개의 좋아요

확신은 없는데, @xrav3nz님은 어떻게 생각하세요?

4개의 좋아요

지금은 신경 쓰지 않는 게 좋겠습니다.

frame-ancestors는 Discourse/Rails가 이미 강제하는 X-Frame-Options 헤더와 유사합니다. 현재 이 헤더는 sameorigin으로 설정되어 있으며, 이는 CSP 지시어의 self 옵션과 대략적으로 같습니다.

self 이외의 특정 도메인을 화이트리스트로 추가해야 하는 경우가 아니라면, 지금 frame-ancestors를 구현하더라도 큰 이점이 없을 것 같습니다.

8개의 좋아요

저도 이 의견에 동의합니다.

모든 가능한 취약점 스캐너의 "이슈"를 추격하다 보면, 위험 대비 효과가 매우 작은데도 중요한 기능들이 깨질 수 있습니다.

이론적으로는 취약점이지만 실제로는 거의 악용되지 않거나, 매우 특수한 시나리오에서만 악용될 수 있는 "이론적 취약점"이 많습니다. 제 지식 범위 내에서 이러한 유형의 잠재적 취약점은 Discourse 사이트에서 악용된 적이 없었습니다. 그리고 어떤 결과도 초래하지 않은 “이론적” 취약점을 "수정"하는 것은 권장하지 않습니다.

수십 년간 사이버 보안 전문가로 활동해 온 저의 소견입니다. 사이버 보안 리스크 관리의 기초에 대해 관심이 있으신 분이 계시면 기꺼이 논의하겠습니다.

5개의 좋아요

방금 CSP frame-ancestors 지시문에 대한 지원을 추가했습니다. 현재는 content security policy frame ancestors 사이트 설정 뒤에 숨겨져 있으며 기본적으로 비활성화되어 있습니다. 기존과 마찬가지로 /admin/customize/embedding을 통해 도메인 목록에 항목을 추가할 수 있습니다.

이 지시문은 다음 릴리스 주기에서 기본적으로 활성화될 예정입니다.

7개의 좋아요