Respuesta rápida
La página de error 429 (Too Many Requests) de un subdominio de una red social Q&A reflejaba parte de la URL dentro del bloque <script> de Google Analytics, sin sanitizar. Como el input ya estaba en contexto JavaScript, no hacía falta romper HTML: bastaba romper el string literal con '-alert(document.domain)-'. Reflected XSS clásico, pero el vector — el snippet de GA — es interesante.
1. Contexto — la página de error 429
Cuando un usuario realiza demasiadas peticiones a un subdominio de la plataforma, el servidor devuelve una página de error 429. Esta página incluye un snippet de Google Analytics que toma parte de la URL como input para construir sus llamadas de tracking.
El problema es que ese valor de la URL se inserta directamente dentro del bloque <script> de Google Analytics sin ningún tipo de sanitización, convirtiéndose en un punto de inyección de JavaScript.
2. El payload y la URL vulnerable
https://subdomain.target.tld/'-alert(document.domain)-'
La parte de la URL después de la barra ('-alert(document.domain)-') se refleja directamente dentro del script de Google Analytics en la respuesta HTTP.
3. El código vulnerable en la respuesta
<script type="text/javascript">
...
ga('set', 'dimension1', 'board-'-alert(document.domain)-'');
ga('set', 'dimension2', 'False');
ga('set', 'dimension3', 'False');
});
});
</script>
El valor de la URL se concatena dentro de un string literal JavaScript entre comillas simples. El payload cierra la comilla de apertura con ', inyecta la llamada a alert(document.domain) con concatenación de strings (-alert(document.domain)-), y reabre la comilla para que el resto del código sea sintácticamente válido.
Desglose del payload
dimension1', 'board- ← string original hasta donde llega el input
'-alert(document.domain)-' ← input del atacante
La secuencia resultante en el script es:
ga('set', 'dimension1', 'board-' - alert(document.domain) - '');
JavaScript evalúa -alert(document.domain)- como una operación aritmética, lo que fuerza la evaluación de alert(document.domain) como parte de la expresión. El resultado aritmético no importa — lo que importa es que alert(document.domain) se ejecuta.
4. Condición para triggear el XSS
La página de error 429 solo se muestra cuando el servidor detecta demasiadas peticiones desde el mismo cliente. Para que la víctima cargue la página vulnerable, primero tiene que estar en rate limit. En la práctica esto se consigue de varias formas:
- El atacante envía muchas peticiones previas desde la IP de la víctima (no trivial).
- La víctima ya está en rate limit de forma natural por su propio uso.
- Se envía directamente el link cuando ya está en estado 429.
5. Clasificación técnica
| Campo | Valor |
|---|---|
| Tipo de vulnerabilidad | Reflected XSS — Input reflejado en bloque <script> |
| CWE | CWE-79 — Improper Neutralization of Input |
| Vector | Segmento de URL reflejado en snippet de Google Analytics |
| Persistencia | Reflected — se activa solo al cargar la URL maliciosa |
| Condición previa | Estado 429 (rate limit) activo en el cliente |
6. Notas técnicas
- El XSS está dentro de un bloque
<script>existente, no en un atributo HTML. Esto lo hace especialmente directo: no hace falta salir de un contexto HTML, el input ya está dentro de un contexto JavaScript donde cualquier código es ejecutable directamente. - El vector es el snippet de Google Analytics, un bloque de código de terceros que la plataforma incluía en sus páginas de error. La superficie de ataque está en cómo la app construía los parámetros pasados a
ga()usando datos de la URL sin escapar. - La condición del 429 limita ligeramente la explotabilidad respecto a un Reflected XSS sin condiciones previas, pero no la elimina — la URL maliciosa sigue siendo crafteada por el atacante y compartible.
Lección general
Cuando un valor del cliente acaba dentro de un bloque <script>, el vector ya está medio cocinado: solo necesitas romper el contexto del string literal que lo contiene. No mires solo HTML cuando audites páginas de error o snippets de tracking — el contexto JavaScript es donde menos sanitización suele haber.
Labs relacionados
Practica reflected XSS en script blocks y bypasses con concatenación aritmética: labs de XSS.
Practica esto en un lab
Xss
Sigue aprendiendo · cuenta gratis
Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.
Artículos relacionados
Stored XSS en nombres de plantilla — del campo más aburrido al domain takeover
Un campo de título en una plantilla, sin sanitizar, en una sesión con permisos sobre dominios. Bounty real de €1.200. Cómo encontrar XSS donde nadie mira.
DOM XSS — gadgets, postMessage handlers y CVE-2025-59840
DOM XSS no es solo innerHTML. Sources/sinks, gadget chains via toString(), postMessage handlers sin origin check, hash-based routing rotos.
postMessage — vulnerabilidades comunes: origin bypass, XSS sink, IDOR cross-window
Cómo identificar y explotar vulnerabilidades en window.postMessage(): listeners sin validación de origin, payloads JSON inseguros que llegan a DOM XSS, IDOR cross-origin.