dom-vulnerability-detection · git:20260706.00661a3 · 2026-07-06 · sha256 5ce0c813cd5f8af5
dom-vulnerability-detection git:20260706.00661a3B
Immutable. This exact content is served forever at /api/v1/blob/5ce0c813cd5f8af5.
---
name: dom-vulnerability-detection
description: DOM-based XSS and client-side vulnerability detection via dynamic analysis -- trace attacker-controlled sources to dangerous sinks, audit postMessage handlers, test CSTI in Angular/Vue/htmx, and check for DOM clobbering. Use when auditing JavaScript code, reviewing client-side security, analyzing DOM manipulation, or testing postMessage handlers.
---
## Sources (attacker-controlled input)
`location.hash`, `location.search`, `location.href`, `document.URL`, `document.referrer`, `window.name`, `postMessage event.data`, `document.cookie`, `localStorage`/`sessionStorage`
## Sinks (dangerous output)
`innerHTML`, `outerHTML`, `document.write()`, `eval()`, `setTimeout(string)`, `new Function()`, `location.href=`, `location.assign()`, `element.src=`, `jQuery.html()`, `$.globalEval()`, `v-html`, `ng-bind-html`, `dangerouslySetInnerHTML`
## Analysis workflow
### 1. Find sources reaching sinks
```bash
# Search for dangerous sinks in JS files
rg "innerHTML|outerHTML|document\.write|\.html\(" --type js --type ts -n src/
rg "eval\(|setTimeout\(|setInterval\(|new Function\(" --type js -n src/
# Search for postMessage handlers without origin checks
rg "addEventListener.*message" --type js -n src/
```
### 2. Trace data flow
For each sink found, trace backwards: does attacker-controlled input reach it?
- Direct: `element.innerHTML = location.hash.slice(1)`
- Via variable: `const data = getParam('q'); ... el.innerHTML = data`
- Via storage: `localStorage.setItem('x', userInput); ... el.innerHTML = localStorage.getItem('x')`
**Checkpoint:** For each sink, document: source -> transformations -> sink. If no attacker-controlled source reaches the sink, mark as not exploitable and move on.
### 3. Check sanitization
If sanitization exists, verify it is adequate:
- DOMPurify -> check version, config (is `ALLOW_UNKNOWN_PROTOCOLS` set?)
- Custom sanitizer -> see `custom-sanitizer-audit` skill
- Framework auto-escaping -> verify not bypassed by `v-html`, `dangerouslySetInnerHTML`, `[innerHTML]`
### 4. Audit postMessage handlers
```javascript
// VULNERABLE -- no origin check
window.addEventListener('message', (e) => {
document.getElementById('output').innerHTML = e.data.html;
});
// SAFE -- origin validated
window.addEventListener('message', (e) => {
if (e.origin !== 'https://trusted.com') return;
// ... process e.data
});
```
**targetOrigin bypass via IP normalization:** When `postMessage(data, targetOrigin)` uses regex validation like `/https?:\/\/[^.]+[.]target[.]com/`, the `[^.]+` class matches `/` -- so `http://2130706433/.target.com` passes the regex. The browser's URL parser then normalizes the integer IP to `127.0.0.1` and sends the message to `http://127.0.0.1` (attacker-controlled). Same technique works with hex (`0x7f000001`) and octal IP forms. Check: does the sender validate `targetOrigin` with regex rather than strict string equality? If yes, test integer IP + path injection.
**Checkpoint:** For each handler, verify: (1) strict `e.origin` equality check exists, (2) no `window.origin` comparison, (3) no `startsWith`/`endsWith` on origin, (4) data is not passed to dynamic execution (`window[data.func]`).
### 5. Test CSTI (Client-Side Template Injection)
- **AngularJS**: `{{constructor.constructor('alert(1)')()}}`
- **Vue.js**: check if user input reaches `v-html` or template interpolation
- **htmx**: `hx-get`, `hx-post` with user-controlled URLs
### 6. Check browser quirks
- **DOM clobbering**: `<form id="x"><input name="action" value="javascript:alert(1)">` -- overwrites `document.x.action`
- **Mutation XSS**: HTML that passes sanitizer but mutates in browser DOM -- see `dompurify-mxss-bypass` skill
- **Prototype pollution**: `__proto__` in URL params or JSON reaching `Object.assign`/spread
### 7. Verify exploitability
Build a PoC proving attacker-controlled input triggers the sink:
```
https://target.com/page#<img src=x onerror=alert(document.domain)>
```
**Checkpoint:** Confirm payload executes (not just reflected). Check CSP -- if blocked, see `csp-bypass` skill.
## Chain With
- `csp-bypass` (CSP blocks execution), `dompurify-mxss-bypass` (DOMPurify present), `custom-sanitizer-audit` (homegrown sanitizer), `self-xss-escalation` (payload only fires in own session)