Mitigare gli attacchi XSS con Content Security Policy

:bookmark: Questa guida spiega come utilizzare la Content Security Policy (CSP) per mitigare gli attacchi di Cross-Site Scripting (XSS) in Discourse. Copre le basi della CSP, la configurazione e le migliori pratiche.

:person_raising_hand: Livello utente richiesto: Amministratore

Riepilogo

La Content Security Policy (CSP) è una funzionalità di sicurezza fondamentale in Discourse che aiuta a proteggere contro gli attacchi di Cross-Site Scripting (XSS) e altre iniezioni. Questa guida copre le basi della CSP, come viene implementata in Discourse e come configurarla per il tuo sito.

Cos’è la Content Security Policy?

La Content Security Policy è un ulteriore livello di sicurezza che aiuta a rilevare e mitigare determinati tipi di attacchi, inclusi gli attacchi di Cross-Site Scripting (XSS) e di iniezione di dati. La CSP funziona specificando quali origini di contenuto sono considerate attendibili e istruendo il browser a eseguire o rendere solo le risorse provenienti da tali origini attendibili.

L’XSS rimane una delle vulnerabilità web più comuni. Implementando la CSP, Discourse consente il caricamento e l’esecuzione di script solo da fonti attendibili, riducendo significativamente il rischio di attacchi XSS.

Implementazione della CSP in Discourse

A partire dalla versione 3.3.0.beta1 di Discourse, l’implementazione della CSP utilizza la modalità ‘strict-dynamic’. Questo approccio impiega un singolo valore nonce- e la parola chiave strict-dynamic nella direttiva script-src. Tutti i tag \u003cscript\u003e iniziali nel core e nei temi ricevono automaticamente l’attributo nonce= appropriato.

La policy predefinita include le seguenti direttive:

  • script-src: Specifica le origini valide per gli script JavaScript
  • worker-src: Specifica le origini valide per gli script ServiceWorker
  • object-src: Blocca l’esecuzione di plugin (Flash, Java, ecc.)
  • base-uri: Restringe gli URL per gli elementi \u003cbase\u003e
  • manifest-src: Restringe gli URL per i manifest delle app web
  • frame-ancestors: Controlla quali siti possono incorporare la tua istanza di Discourse in un iframe
  • upgrade-insecure-requests: Aggiorna automaticamente le richieste HTTP a HTTPS (incluso quando force_https è abilitato)

Configurazione della CSP in Discourse

Impostazioni disponibili

  • content_security_policy: Abilita o disabilita la CSP (predefinito: on)
  • content_security_policy_report_only: Abilita la modalitĂ  Report-Only della CSP (predefinito: off)
  • content_security_policy_script_src: Consente di estendere la direttiva script-src predefinita
  • content_security_policy_frame_ancestors: Abilita la direttiva frame_ancestors (predefinito: on)

Come abilitare la CSP

  1. Vai al pannello di Amministrazione
  2. Accedi alle impostazioni di Sicurezza
  3. Trova l’impostazione content_security_policy e assicurati che sia abilitata

Si consiglia di iniziare con la modalitĂ  Report-Only della CSP per identificare eventuali problemi prima di abilitare completamente la CSP:

  1. Abilita l’impostazione content_security_policy_report_only
  2. Monitora la console del browser per le violazioni della CSP
  3. Risolvi eventuali violazioni legittime estendendo la CSP come necessario
  4. Una volta sicuro che non ci siano falsi positivi, disabilita la modalitĂ  Report-Only e abilita completamente la CSP

Estendere la CSP predefinita

Se è necessario consentire origini script aggiuntive, è possibile estendere la direttiva script-src utilizzando l’impostazione content_security_policy_script_src. È possibile aggiungere:

  • Hash delle origini
  • 'wasm-unsafe-eval'
  • 'unsafe-eval' (usare con cautela)

Ad esempio:

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

:warning: Fai attenzione quando aggiungi 'unsafe-eval' o altre direttive permissive, poiché possono ridurre l’efficacia della CSP.

CSP e integrazioni di terze parti

Quando si utilizzano servizi di terze parti come Google Tag Manager, Google Analytics o servizi pubblicitari, potrebbe essere necessario regolare le impostazioni della CSP. Nella maggior parte dei casi con Discourse versione 3.3.0.beta1 o successive, gli script esterni dovrebbero funzionare senza configurazioni aggiuntive grazie all’implementazione della CSP ‘strict-dynamic’.

Se si verificano problemi, potrebbe essere necessario:

  1. Identificare le origini script richieste monitorando la console del browser
  2. Aggiungere le origini necessarie all’impostazione content_security_policy_script_src
  3. Per integrazioni complesse come i servizi pubblicitari che caricano risorse esterne, potrebbe essere necessario abilitare il rendering cross-domain (Esempio di PR da discourse-adplugin che lo fa).

Migliori pratiche

  1. Inizia con la modalitĂ  Report-Only della CSP per identificare potenziali problemi
  2. Stringi gradualmente la CSP man mano che risolvi le violazioni legittime
  3. Rivedi regolarmente le impostazioni della CSP e adattale secondo necessitĂ 
  4. Fai attenzione quando aggiungi direttive permissive come 'unsafe-eval' o 'wasm-unsafe-eval'
  5. Mantieni aggiornata la tua istanza di Discourse per beneficiare dei miglioramenti piĂš recenti della CSP

Domande frequenti

D: Ricevo molti rapporti di violazione della CSP. Dovrei preoccuparmi?
R: Molte violazioni della CSP sono falsi positivi, spesso causati da estensioni del browser o altri script non correlati. Concentrati sulla risoluzione delle violazioni relative alla funzionalitĂ  del tuo sito.

D: Posso utilizzare la CSP con Google AdSense o altre reti pubblicitarie?
R: SĂŹ, ma potrebbe essere necessario utilizzare impostazioni della CSP piĂš permissive. Inizia con la modalitĂ  Report-Only e regola le impostazioni in base alle violazioni riportate.

D: Come risolvo i problemi relativi alla CSP?
R: Utilizza gli strumenti per sviluppatori del browser per monitorare la console per i messaggi di violazione della CSP. Questi ti aiuteranno a identificare quali risorse vengono bloccate e perchĂŠ.

Risorse aggiuntive

56 Mi Piace
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
How to restart Discourse after server reboot?
Interactive SVG using <object>?
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
A strange question about google ad display in my site
How to pass a component setting as a value to an attribute?
Need help integrating code wrote on Edittext to the Discourse
Issue with Activate Account Page After Update to 3.4.0 (Blank Page)
"Unsafe JavaScript attempt to initiate navigation"
(Superseded) Experimenting with a 'strict-dynamic' Content Security Policy (CSP)
Can't get script tag to work in landing pages plugin due to content-security-policy
How to embed Razorpay subscription button with CSP restrictions
Any approved method for adding Javascript before body close?
Can I add a snippet to the header?
JS script is not loading
How do we fire scripts after topic HTML is rendered in DOM?
Iframe issue without URL
Where to place ad script?
We couldn't find the code on your site
Javascript not working in customised areas
Difficulties in correctly adding external JavaScript
Report Only CSP Violations
Nginx config in Discourse Docker?
EPN Smart Links
Discourse 2.2.0.beta9 Release Notes
"Refused to load the script" when adding adsense
Why Cookie Consent Doesn't Show Up?
[DigitalOcean] hostname having "www" in A records showing blank page
[DigitalOcean] hostname having "www" in A records showing blank page
Communities with embedded Twitter Feeds
How to add analytics and pixel scripts avoiding Content Security Policy (XSS)
When install html script facing issue?
Discourse 2.4.0.beta10 Release Notes
Add CSP sources to the plugin
IP does not redirect to domain, domain shows white page
DISCOURSE_CDN_URL causes content security policy violations?
Header content is missing due to CSP
How to insert something right after <head>?
How can I embed tracking JS into Discourse
How do I integrate ? cookiebot.com in meinen Forum?
Adding Cookie Consent Banner
Confused about remotely loaded javascript content
User input validation
Custom JS script in theme component not loading

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

13 Mi Piace

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 Mi Piace

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 Mi Piace

I’m having trouble configuring, which recommendation?

my forum: forum.meuxbox.com.br

link: White blank advertisement

2 Mi Piace

It depends on what URLs your ads are requesting. You can look at your browser’s console to see them.

See also the relevant section from the OP:

4 Mi Piace

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

this solves the problem of the error

6 Mi Piace

Could you please add a Feature-Policy?

This is the one that I am using for more than a year now. (host 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’;”;


Would it make sense to add the following to Content-Security-Policy header? This is what I am successfully using (added by host nginx, on top of discourse built-in 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 Mi Piace

Given that the spec is still a draft, I don’t expect us to implement it at this time. The Mozilla website you listed even says:

The Feature-Policy header is still in an experimental state, and is subject to change at any time. Be wary of this when implementing Feature Policy on your website.

8 Mi Piace

vibrate 'self' - likes on Android trigger a faint, brief vibration.

10 Mi Piace

I just installed discourse 2.6.0.beta1 . Do i need to reconfigure this? Thank you

1 Mi Piace

You do not, this is on by default. You only need to modify the config if you’re seeing external resources that are being blocked and you want them to run.

4 Mi Piace

I am running 2.6.0 beta 2.

I use the following services in the forums:

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

Currently, I am using report CSP only while I try to resolve all open issues.

Here are my CSP settings:

With the current settings, I am still receiving LOTS of CSP errors. Some seem like they can be ignored. However this one is boggling me as I have domain declared in the CSP settings.

Am I missing something?

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 Mi Piace

There are lots and lots of false positives in CSP reporting. See my reply above for more details.

You don’t need most of the rules you have set in your screenshot, just https: and unsafe-inline are enough, they allow all scripts starting with https and all inline scripts. Try cleaning up your CSP sources setting and enabling CSP (without reporting), it should work.

8 Mi Piace

Ciao, mi chiedevo solo: la direttiva Content-Security-Policy: frame-ancestors ‘none’ è quella che “sarà inclusa nei prossimi aggiornamenti” come menzionato dall’OP?

È possibile aggiungere tale direttiva in qualche modo o devo semplicemente aspettare e non preoccuparmene? Stavo solo eseguendo un esercizio di hardening e questa era l’unica voce aperta / raccomandazione fornita da quello strumento di sicurezza online. Mi dà molta fiducia nella piattaforma, in realtà: ottimo lavoro, ragazzi!

5 Mi Piace

Non sono sicuro – cosa ne pensi @xrav3nz?

4 Mi Piace

Direi di non preoccupartene per ora.

frame-ancestors è simile all’header X-Frame-Options che Discourse/Rails applica già. L’intestazione è attualmente impostata su sameorigin – più o meno come l’opzione self della direttiva CSP.

Secondo me non guadagneremmo molto implementando frame-ancestors in questo momento, a meno che non dobbiamo supportare l’inserimento in whitelist di domini specifici diversi da self.

8 Mi Piace

Sono d’accordo.

Inseguire ogni possibile “problema” rilevato dagli scanner di vulnerabilità può finire per rompere cose importanti, con un rapporto rischio-beneficio molto basso.

Esistono molte “vulnerabilità teoriche” che vengono raramente sfruttate o solo in scenari molto specifici. A quanto mi risulta, questo tipo di potenziali vulnerabilità non è mai stato sfruttato su un sito Discourse; sconsiglio quindi di “risolvere” problemi che sono vulnerabilità solo “in teoria” e che non hanno mai portato a violazioni di alcuna rilevanza.

Questo è il mio parere, in quanto professionista della cybersecurity con decenni di esperienza. Sono disponibile a discutere le basi della gestione del rischio in ambito cybersecurity, se qualcuno è interessato.

5 Mi Piace

Abbiamo appena implementato il supporto per la direttiva CSP frame-ancestors. Al momento è disabilitata di default e accessibile tramite l’impostazione del sito content security policy frame ancestors. Puoi aggiungere domini all’elenco come sempre tramite /admin/customize/embedding.

Questa direttiva sarĂ  abilitata di default nel prossimo ciclo di rilascio.

7 Mi Piace