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

Nivel AvanzadoPremium

Secondary context XSS — encontrando XSS donde el primary parsing no lo detecta

Cuando el primary parsing escapa pero un secondary context (markdown render, BBCode, custom template engine) interpreta de nuevo, abriendo XSS oculto a sanitizers tradicionales.

Gorka El Bochi11 de mayo de 202615 min

Respuesta rápida

Secondary context XSS surge cuando el input pasa por dos pipelines de parsing: el primero escapa lo que entiende como peligroso, el segundo re-interpreta lo que el primero ignoró. Markdown renderers, BBCode parsers, custom template engines, latex-to-HTML, SVG inline serialization — todos son secondary contexts donde los caracteres "seguros" del primary se convierten en payloads. El truco: encontrar el delta entre lo que el sanitizer mira y lo que el render finalmente emite.


El modelo de threat

Pipeline típico vulnerable:

css
User input[Primary parser]   ← escapa <, >, " según context HTML[Secondary parser] ← markdown / BBCode / template engine → emite NUEVO HTML
    ↓
DOM render        ← ejecuta el HTML resultante

El sanitizer suele aplicarse en el primary o en el output final. Si solo aplica en el primary, el secondary tiene libertad para emitir lo que quiera. Si aplica en el output final, el sanitizer está protegido — pero solo si conoce todos los outputs posibles del secondary.


Markdown — el secondary context #1

Markdown→HTML es el caso más explotado. CommonMark/markdown-it/marked/showdown todos tienen comportamientos diferentes en edge cases.

markdown
[click me](javascript:alert(1))

Algunos parsers emiten:

html
<a href="javascript:alert(1)">click me</a>

Solución típica: parser bloquea javascript:. Pero:

markdown
[click me](javascript&#58;alert(1))
[click me](javascript&#x3a;alert(1))
[click me](java&#9;script:alert(1))
[click me](java%0ascript:alert(1))

CommonMark spec dice "preserve the URL as-is, decode HTML entities at render time". Algunos parsers decodan, otros no. La inconsistencia es el bypass.

Vector — HTML raw inline

Por defecto la mayoría de parsers permiten HTML raw mezclado con markdown:

markdown
Hola

<img src=x onerror=alert(1)>

mundo

Mitigación común: pasar el output por DOMPurify. Pero DOMPurify trabaja en el HTML final, no en markdown. Si el parser emite HTML válido que DOMPurify acepta + un side effect, el chain pasa.

markdown
[click][autolink]

[autolink]: javascript:alert(1)

Reference-style links. El parser puede decodar el destino later, después del sanitize.

Vector — code blocks que escapan al render

Markdown permite lang hint en code blocks. Un payload típico es un bloque "html" con un payload dentro:

css
```html
<img src=x onerror=alert(1)>
```

Si el renderer pasa el code block through un syntax highlighter (Prism, highlight.js, Shiki) que emite HTML "structured", el sanitizer del output puede asumir que está dentro de <pre><code> y permitir tags... y algunos highlighters serializan mal.

Sigue leyendo el chain completo

La parte que falta incluye el PoC paso a paso, código de explotación y la cadena completa que llevó al impacto. Disponible para suscriptores.

Practica esto en un lab

Secondary Context

Resolver

Sigue aprendiendo · cuenta gratis

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

Crear cuenta

Artículos relacionados