Mitiga ataques XSS con Content Security Policy

:bookmark: Esta guía explica cómo utilizar la Política de Seguridad de Contenido (CSP) para mitigar los ataques de Cross-Site Scripting (XSS) en Discourse. Cubre los conceptos básicos de CSP, su configuración y las mejores prácticas.

:person_raising_hand: Nivel de usuario requerido: Administrador

Resumen

La Política de Seguridad de Contenido (CSP) es una función de seguridad crucial en Discourse que ayuda a proteger contra ataques de Cross-Site Scripting (XSS) y otras inyecciones. Esta guía cubre los fundamentos de CSP, cómo se implementa en Discourse y cómo configurarla para tu sitio.

¿Qué es la Política de Seguridad de Contenido?

La Política de Seguridad de Contenido es una capa adicional de seguridad que ayuda a detectar y mitigar ciertos tipos de ataques, incluidos los ataques de Cross-Site Scripting (XSS) y las inyecciones de datos. CSP funciona especificando qué fuentes de contenido se consideran de confianza e instruyendo al navegador para que solo ejecute o renderice recursos desde esas fuentes confiables.

El XSS sigue siendo una de las vulnerabilidades web más comunes. Al implementar CSP, Discourse permite que solo los scripts de fuentes confiables se carguen y ejecuten, reduciendo significativamente el riesgo de ataques XSS.

Implementación de CSP en Discourse

A partir de la versión 3.3.0.beta1 de Discourse, esta plataforma implementa una CSP de ‘strict-dynamic’. Este enfoque utiliza un único valor nonce- y la palabra clave strict-dynamic en la directiva script-src. Todas las etiquetas \u003cscript\u003e iniciales en el núcleo y los temas reciben automáticamente el atributo nonce= apropiado.

La política predeterminada incluye las siguientes directivas:

  • script-src: Especifica las fuentes válidas para JavaScript.
  • worker-src: Especifica las fuentes válidas para los scripts de ServiceWorker.
  • object-src: Bloquea la ejecución de plugins (Flash, Java, etc.).
  • base-uri: Restringe las URL para los elementos \u003cbase\u003e.
  • manifest-src: Restringe las URL para los manifiestos de aplicaciones web.
  • frame-ancestors: Controla qué sitios pueden incrustar tu instancia de Discourse en un iframe.
  • upgrade-insecure-requests: Actualiza automáticamente las solicitudes HTTP a HTTPS (incluido cuando force_https está habilitado).

Configuración de CSP en Discourse

Configuraciones disponibles

  • content_security_policy: Habilita o deshabilita CSP (predeterminado: on).
  • content_security_policy_report_only: Habilita el modo CSP Report-Only (predeterminado: off).
  • content_security_policy_script_src: Te permite extender la directiva script-src predeterminada.
  • content_security_policy_frame_ancestors: Habilita la directiva frame_ancestors (predeterminado: on).

Cómo habilitar CSP

  1. Navega a tu panel de Administración.
  2. Ve a la configuración de Seguridad.
  3. Busca la configuración content_security_policy y asegúrate de que esté habilitada.

Se recomienda comenzar con el modo CSP Report-Only para identificar posibles problemas antes de habilitar CSP completamente:

  1. Habilita la configuración content_security_policy_report_only.
  2. Monitorea la consola de tu navegador en busca de violaciones de CSP.
  3. Aborda cualquier violación legítima extendiendo la CSP según sea necesario.
  4. Una vez que estés seguro de que no hay falsos positivos, deshabilita el modo Report-Only y habilita CSP completamente.

Extender la CSP predeterminada

Si necesitas permitir fuentes de scripts adicionales, puedes extender la directiva script-src utilizando la configuración content_security_policy_script_src. Puedes agregar:

  • Fuentes basadas en hash.
  • 'wasm-unsafe-eval'.
  • 'unsafe-eval' (úsalo con precaución).

Por ejemplo:

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

:warning: Ten cuidado al agregar 'unsafe-eval' u otras directivas permisivas, ya que pueden reducir la efectividad de CSP.

CSP e integraciones de terceros

Al utilizar servicios de terceros como Google Tag Manager, Google Analytics o servicios de publicidad, es posible que debas ajustar tu configuración de CSP. En la mayoría de los casos con Discourse versión 3.3.0.beta1 o posterior, los scripts externos deberían funcionar sin configuración adicional debido a la implementación de CSP de ‘strict-dynamic’.

Si encuentras problemas, es posible que debas:

  1. Identificar las fuentes de scripts necesarias monitoreando la consola de tu navegador.
  2. Agregar las fuentes necesarias a la configuración content_security_policy_script_src.
  3. Para integraciones complejas como servicios de publicidad que cargan recursos externos, es posible que debas habilitar la renderización entre dominios (Ejemplo de PR de discourse-adplugin que hace esto).

Mejores prácticas

  1. Comienza con el modo CSP Report-Only para identificar posibles problemas.
  2. Ajusta progresivamente tu CSP a medida que resuelves las violaciones legítimas.
  3. Revisa regularmente tu configuración de CSP y ajústala según sea necesario.
  4. Ten cuidado al agregar directivas permisivas como 'unsafe-eval' o 'wasm-unsafe-eval'.
  5. Mantén tu instancia de Discourse actualizada para beneficiarte de las últimas mejoras de CSP.

Preguntas frecuentes

P: Estoy viendo muchos informes de violaciones de CSP. ¿Debería preocuparme?
R: Muchas violaciones de CSP son falsos positivos, a menudo causados por extensiones del navegador u otros scripts no relacionados. Concéntrate en abordar las violaciones relacionadas con la funcionalidad de tu sitio.

P: ¿Puedo usar CSP con Google AdSense u otras redes publicitarias?
R: Sí, pero es posible que debas usar configuraciones de CSP más permisivas. Comienza con el modo Report-Only y ajusta tu configuración en función de las violaciones reportadas.

P: ¿Cómo soluciono problemas de CSP?
R: Utiliza las herramientas de desarrollador de tu navegador para monitorear la consola en busca de mensajes de violación de CSP. Estos te ayudarán a identificar qué recursos están siendo bloqueados y por qué.

Recursos adicionales

56 Me gusta
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 Me gusta

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 Me gusta

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 Me gusta

I’m having trouble configuring, which recommendation?

my forum: forum.meuxbox.com.br

link: White blank advertisement

2 Me gusta

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 Me gusta

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

this solves the problem of the error

6 Me gusta

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 Me gusta

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 Me gusta

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

10 Me gusta

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

1 me gusta

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 Me gusta

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 Me gusta

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 Me gusta

Hola, solo quería preguntar: ¿la directiva Content-Security-Policy: frame-ancestors ‘none’ es la que “se incluirá en futuras actualizaciones” según mencionó el autor del tema?

¿Es posible agregar esa directiva de alguna manera o debería simplemente esperar y no preocuparme por ello? Solo estaba realizando un ejercicio de endurecimiento y esta fue la única recomendación pendiente que señaló esa herramienta de seguridad en línea. De hecho, esto me da mucha confianza en la plataforma. ¡Buen trabajo, chicos!

5 Me gusta

No estoy seguro, ¿qué opinas @xrav3nz?

4 Me gusta

Diría que no te preocupes por ello por ahora.

frame-ancestors es similar a la cabecera X-Frame-Options, que Discourse/Rails ya aplica. La cabecera está configurada actualmente en sameorigin, más o menos lo mismo que la opción self de la directiva CSP.

En mi opinión, no ganaremos mucho al implementar frame-ancestors en este momento, a menos que necesitemos permitir la lista blanca de dominios específicos distintos de self.

8 Me gusta

Estoy de acuerdo con esto.

Perseguir cada “problema” que detecten los escáneres de vulnerabilidades puede terminar rompiendo cosas importantes, con una relación riesgo-beneficio muy baja.

Existen muchas “vulnerabilidades teóricas” que rara vez se explotan o solo pueden serlo bajo un escenario muy específico. A mi conocimiento, este tipo de vulnerabilidades potenciales nunca han sido explotadas en un sitio de Discourse; y desaconsejaría “arreglar” cosas que son “teóricamente” una vulnerabilidad y que no han resultado en ninguna brecha de consecuencias.

Esa es mi opinión, aportada por un profesional de la ciberseguridad con varias décadas de experiencia. Estaré encantado de discutir los conceptos básicos de la gestión de riesgos en ciberseguridad si alguien está interesado.

5 Me gusta

Acabamos de implementar el soporte para la directiva frame-ancestors de CSP. Por ahora, está desactivada de forma predeterminada y protegida por la configuración del sitio “content security policy frame ancestors”. Puedes agregar dominios a la lista a través de /admin/customize/embedding, como siempre.

Esta directiva se habilitará de forma predeterminada en el próximo ciclo de lanzamiento.

7 Me gusta