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.

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

hunters training
711

hunters training

labs from real reports
55

labs from real reports

completions
1,205

completions

in bounties practiced
$213,970

in bounties practiced

46 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy