seguridadwebdesarrollo

Content Security Policy: frena el XSS con cabeceras HTTP

Una política de seguridad de contenido es la defensa más eficaz que ofrece el navegador contra los ataques de scripts entre sitios. Aquí tienes cómo funciona, qué significan sus directivas y cómo montar una sin romper tu web.

·7 min de lectura

Qué es el XSS y por qué importa la CSP

El scripting entre sitios (XSS) es un ataque que inyecta JavaScript malicioso en una página web de confianza. Ese script se ejecuta en el navegador de la víctima con los mismos privilegios que la página legítima: puede leer cookies, hacer peticiones a la API, robar datos y redirigir a la gente.

Las defensas clásicas (sanear lo que se recibe, escapar lo que se muestra) son necesarias pero no suficientes. Basta un valor sin escapar, una biblioteca de terceros con una vulnerabilidad o una plantilla mal configurada para abrir la puerta.

Una política de seguridad de contenido (CSP) es una segunda línea de defensa en el propio navegador. Aunque alguien consiga inyectar un script, la CSP le dice al navegador qué scripts pueden ejecutarse, y los que no vengan de fuentes aprobadas simplemente no se ejecutan.

Cómo funciona la CSP

La CSP se envía como una cabecera de respuesta HTTP (o como una etiqueta <meta>, aunque la cabecera es preferible):

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com

El navegador lee la política y, con cada recurso que la página intenta cargar (scripts, hojas de estilo, imágenes, tipografías, peticiones a API), comprueba si la fuente está permitida. Lo que la incumpla se bloquea y, si quieres, se notifica.

Las directivas principales

**default-src**: el valor por defecto para las directivas que no especifiques. Un buen punto de partida es default-src 'self', que obliga a que todos los recursos vengan del mismo origen.

  • 'self': solo scripts del mismo origen
  • 'none': ningún script
  • https://cdn.example.com: un dominio concreto
  • 'nonce-{random}': los scripts con un atributo nonce que coincida

**style-src**: controla las fuentes de CSS. Los estilos en línea se bloquean por defecto.

**img-src**: controla las fuentes de imagen. img-src 'self' data: permite imágenes del mismo origen y URI de datos.

**connect-src**: controla a qué direcciones se puede llamar con fetch, XMLHttpRequest o WebSocket.

**frame-src**: controla qué orígenes se pueden incrustar en marcos. frame-src 'none' impide que tu página se incruste en otra, lo que reduce el riesgo de secuestro de clics.

**object-src**: controla los complementos (<object>, <embed>). Ponlo siempre en 'none' salvo que tengas un motivo concreto.

**base-uri**: restringe el elemento <base>. Ponlo en 'none' o 'self' para evitar los ataques por inyección de esa etiqueta.

El patrón nonce (la forma correcta de permitir scripts en línea)

Una CSP estricta bloquea los scripts en línea (<script>alert('hola')</script>). La manera correcta de permitir algunos es el nonce: un valor aleatorio que se genera en cada petición y que aparece tanto en la cabecera CSP como en el atributo nonce de la etiqueta del script.

El servidor genera: const nonce = crypto.randomBytes(16).toString('base64')

Cabecera: script-src 'nonce-abc123def456'

HTML: <script nonce="abc123def456">...</script>

Quien inyecte una etiqueta de script no puede conocer el nonce, porque cambia en cada petición, así que su script queda bloqueado aunque los scripts en línea con el nonce correcto sí se permitan.

**No uses nunca 'unsafe-inline'**: permite todos los scripts en línea y anula casi toda la protección de la CSP frente al XSS. Se suele añadir para "arreglar" un script bloqueado, pero echa por tierra el propósito.

`'unsafe-eval'` y eval()

eval() y sus parientes (Function(), setTimeout con una cadena como argumento) ejecutan JavaScript arbitrario a partir de texto. La CSP los bloquea por defecto. Algunas bibliotecas (Google Tag Manager y las versiones antiguas de Angular, sobre todo) exigen 'unsafe-eval', lo que debilita bastante la política.

Si tu CSP necesita 'unsafe-eval', plantéate si esa biblioteca es imprescindible o si hay otra forma de hacerlo.

El modo de solo informe para desplegar sin riesgo

Despliega primero la CSP con la cabecera de solo informe:

Content-Security-Policy-Report-Only: [política]; report-uri https://tu-endpoint-de-informes.com/

Así se registra todo lo que incumple la política sin bloquear nada, y puedes descubrir qué rompería antes de aplicarla de verdad. Cuando ya no queden incumplimientos, cambia a la cabecera que sí bloquea.

Una política inicial práctica

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{nonce-por-petición}'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

El style-src 'unsafe-inline' es una concesión práctica: bloquear los estilos en línea rompe muchos frameworks de interfaz. Y frame-ancestors 'none' es la defensa contra el secuestro de clics, equivalente a la antigua cabecera X-Frame-Options.

El generador de políticas de seguridad de contenido de NoxaKit crea la política a partir de tu configuración. Y el comprobador de cabeceras de seguridad te dice cuáles está sirviendo tu web ahora mismo.

Más artículos