Mitigue ataques XSS com Content Security Policy

:bookmark: Este guia explica como usar a Política de Segurança de Conteúdo (CSP) para mitigar ataques de Cross-Site Scripting (XSS) no Discourse. Ele aborda os fundamentos da CSP, configuração e melhores práticas.

:person_raising_hand: Nível de usuário necessário: Administrador

Resumo

A Política de Segurança de Conteúdo (CSP) é um recurso de segurança crucial no Discourse que ajuda a proteger contra Cross-Site Scripting (XSS) e outros ataques de injeção. Este guia cobre os fundamentos da CSP, como ela é implementada no Discourse e como configurá-la para o seu site.

O que é a Política de Segurança de Conteúdo?

A Política de Segurança de Conteúdo é uma camada adicional de segurança que ajuda a detectar e mitigar certos tipos de ataques, incluindo Cross-Site Scripting (XSS) e ataques de injeção de dados. A CSP funciona especificando quais fontes de conteúdo são consideradas confiáveis e instruindo o navegador a executar ou renderizar recursos apenas dessas fontes confiáveis.

O XSS continua sendo uma das vulnerabilidades web mais comuns. Ao implementar a CSP, o Discourse permite que apenas scripts de fontes confiáveis sejam carregados e executados, reduzindo significativamente o risco de ataques XSS.

Implementação da CSP no Discourse

A partir da versão 3.3.0.beta1 do Discourse, o sistema implementa uma CSP do tipo ‘strict-dynamic’. Essa abordagem utiliza um único valor nonce- e a palavra-chave strict-dynamic na diretiva script-src. Todas as tags \u003cscript\u003e iniciais no núcleo e nos temas recebem automaticamente o atributo nonce= apropriado.

A política padrão inclui as seguintes diretivas:

  • script-src: Especifica fontes válidas para JavaScripts
  • worker-src: Especifica fontes válidas para scripts ServiceWorker
  • object-src: Bloqueia a execução de plugins (Flash, Java, etc.)
  • base-uri: Restringe as URLs para elementos \u003cbase\u003e
  • manifest-src: Restringe as URLs para manifestos de aplicativos web
  • frame-ancestors: Controla quais sites podem incorporar sua instância do Discourse em um iframe
  • upgrade-insecure-requests: Atualiza automaticamente solicitações HTTP para HTTPS (incluído quando force_https está ativado)

Configurando a CSP no Discourse

Configurações disponíveis

  • content_security_policy: Ativa ou desativa a CSP (padrão: ligado)
  • content_security_policy_report_only: Ativa o modo CSP Report-Only (padrão: desligado)
  • content_security_policy_script_src: Permite estender a diretiva script-src padrão
  • content_security_policy_frame_ancestors: Ativa a diretiva frame_ancestors (padrão: ligado)

Como ativar a CSP

  1. Acesse seu painel de Administração
  2. Vá para as configurações de Segurança
  3. Encontre a configuração content_security_policy e certifique-se de que está ativada

Recomenda-se começar com o modo CSP Report-Only para identificar possíveis problemas antes de ativar completamente a CSP:

  1. Ative a configuração content_security_policy_report_only
  2. Monitore o console do navegador para violações da CSP
  3. Resolva quaisquer violações legítimas estendendo a CSP conforme necessário
  4. Assim que tiver certeza de que não há falsos positivos, desative o modo Report-Only e ative completamente a CSP

Estendendo a CSP padrão

Se precisar permitir fontes de script adicionais, você pode estender a diretiva script-src usando a configuração content_security_policy_script_src. Você pode adicionar:

  • Fontes baseadas em hash
  • 'wasm-unsafe-eval'
  • 'unsafe-eval' (use com cautela)

Por exemplo:

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

:warning: Tenha cuidado ao adicionar 'unsafe-eval' ou outras diretivas permissivas, pois elas podem reduzir a eficácia da CSP.

CSP e integrações de terceiros

Ao usar serviços de terceiros como Google Tag Manager, Google Analytics ou serviços de publicidade, talvez seja necessário ajustar suas configurações de CSP. Na maioria dos casos com o Discourse versão 3.3.0.beta1 ou posterior, scripts externos devem funcionar sem configuração adicional devido à implementação da CSP ‘strict-dynamic’.

Se encontrar problemas, talvez seja necessário:

  1. Identificar as fontes de script necessárias monitorando o console do navegador
  2. Adicionar as fontes necessárias à configuração content_security_policy_script_src
  3. Para integrações complexas como serviços de publicidade que carregam recursos externos, talvez seja necessário ativar a renderização entre domínios (Exemplo de PR do discourse-adplugin que faz isso).

Melhores práticas

  1. Comece com o modo CSP Report-Only para identificar possíveis problemas
  2. Aperte gradualmente sua CSP à medida que resolve violações legítimas
  3. Revise regularmente suas configurações de CSP e ajuste conforme necessário
  4. Tenha cuidado ao adicionar diretivas permissivas como 'unsafe-eval' ou 'wasm-unsafe-eval'
  5. Mantenha sua instância do Discourse atualizada para se beneficiar das últimas melhorias da CSP

Perguntas frequentes (FAQs)

P: Estou vendo muitos relatórios de violação da CSP. Devo me preocupar?
R: Muitas violações da CSP são falsos positivos, frequentemente causados por extensões do navegador ou outros scripts não relacionados. Foque em resolver violações relacionadas à funcionalidade do seu site.

P: Posso usar a CSP com o Google AdSense ou outras redes de anúncios?
R: Sim, mas talvez seja necessário usar configurações de CSP mais permissivas. Comece com o modo Report-Only e ajuste suas configurações com base nas violações relatadas.

P: Como soluciono problemas relacionados à CSP?
R: Use as ferramentas de desenvolvedor do seu navegador para monitorar o console em busca de mensagens de violação da CSP. Elas ajudarão a identificar quais recursos estão sendo bloqueados e o motivo.

Recursos adicionais

56 curtidas
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 curtidas

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 curtidas

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 curtidas

I’m having trouble configuring, which recommendation?

my forum: forum.meuxbox.com.br

link: White blank advertisement

2 curtidas

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 curtidas

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

this solves the problem of the error

6 curtidas

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 curtidas

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 curtidas

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

10 curtidas

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

1 curtida

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 curtidas

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 curtidas

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 curtidas

Hi just wondering, the Content-Security-Policy: frame-ancestors ‘none’ directive is one that “will be included in future updates” as mentioned by the OP?

Is it possible to add that directive somehow or should I just wait and not worry about it. Was just doing a hardening exercise and this was the only open item / recommendation by that online security tool. Gives me a lot of confidence in the platform actually, great job guys!

5 curtidas

I’m not sure – what do you think @xrav3nz?

4 curtidas

I’d say don’t worry about it for now.

frame-ancestors is similar to X-Frame-Options header which Discourse/Rails already enforces. The header is currently set to sameorigin – roughly the same as the CSP directive’s self option.

IMO we won’t gain much from implementing frame-ancestors right now, unless we need to support whitelisting specific domains other than self.

8 curtidas

I agree with this.

Chasing every possible vulnerability scanner “issue” can end up breaking things which are important, for a very little risk-benefit ratio.

There are a lot of “vulnerabilities in theory” which are rarely exploited or can only be exploited under a very specific scenario. These types of potential vulnerabilities have never been exploited on a Discourse site, to my knowledge; and I would advise against “fixing” things which are “in theory’” a vulnerability that have not resulted in a breach of any consequence.

That’s my 2 cents as a many decades long cybersecurity professional. I am happy to discuss the basics of cybersecurity risk management if anyone is interested.

5 curtidas

We just landed support for CSP frame-ancestors directive. It’s disabled by default for now behind the content security policy frame ancestors site setting. You can add domains to the list using via /admin/customize/embedding as always.

This directive will be enabled by default in the next release cycle.

7 curtidas