Advanced levelPremium

Secondary context XSS — finding XSS where the primary parsing doesn't detect it

When the primary parsing escapes but a secondary context (markdown render, BBCode, a custom template engine) re-interprets, opening XSS hidden from traditional sanitizers.

Gorka El BochiMay 11, 202615 min

Quick answer

Secondary context XSS arises when the input passes through two parsing pipelines: the first escapes what it understands as dangerous, the second re-interprets what the first ignored. Markdown renderers, BBCode parsers, custom template engines, latex-to-HTML, SVG inline serialization — all are secondary contexts where the "safe" characters of the primary become payloads. The trick: find the delta between what the sanitizer looks at and what the render ultimately emits.


The threat model

Typical vulnerable pipeline:

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

The sanitizer is usually applied at the primary or at the final output. If it's only applied at the primary, the secondary has freedom to emit whatever it wants. If it's applied at the final output, the sanitizer is protected — but only if it knows all the possible outputs of the secondary.


Markdown — the #1 secondary context

Markdown→HTML is the most exploited case. CommonMark/markdown-it/marked/showdown all have different behaviors in edge cases.

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

Some parsers emit:

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

Typical solution: the parser blocks javascript:. But:

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

The CommonMark spec says "preserve the URL as-is, decode HTML entities at render time". Some parsers decode, others don't. The inconsistency is the bypass.

Vector — raw inline HTML

By default most parsers allow raw HTML mixed with markdown:

markdown
Hola

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

mundo

Common mitigation: pass the output through DOMPurify. But DOMPurify works on the final HTML, not on markdown. If the parser emits valid HTML that DOMPurify accepts + a side effect, the chain passes.

markdown
[click][autolink]

[autolink]: javascript:alert(1)

Reference-style links. The parser may decode the destination later, after the sanitize.

Vector — code blocks that escape to the render

Markdown allows a lang hint in code blocks. A typical payload is an "html" block with a payload inside:

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

If the renderer passes the code block through a syntax highlighter (Prism, highlight.js, Shiki) that emits "structured" HTML, the output's sanitizer may assume it's inside <pre><code> and allow tags... and some highlighters serialize incorrectly.

Keep reading the full chain

The remaining part includes the step-by-step PoC, exploitation code and the full chain that led to impact. Available to subscribers.

Free · no account

The checklist I run on every new target

47 checks ordered by cost: first what can get you in trouble, then the cheap stuff, and finally the expensive stuff — which is where the big bounties are. I'll send it to your inbox right now.

Unsubscribe in one click, from any email.

Practice this in a lab

Secondary Context

Solve

Keep learning · free account

Save your progress, unlock advanced payloads and rank your flags.

Create account

Related articles

2,482

hunters training

62

labs from real hacks

1,629

completions

$14,790

paid out for these bugs

11 flags captured this week·Real hacks from HackerOne · YesWeHack · Bugcrowd·No commitment·Free Academy