Cross-Site Scripting (XSS): The Complete Practical Guide

DOM-Based XSS

How DOM-based XSS works: client-side JavaScript reading an attacker-controlled source into a dangerous sink, with the sources, sinks, and how to trace them.

Published

DOM-based XSS is the one that trips people up, because the server is innocent. The vulnerable code is JavaScript already running in the page. It reads something an attacker controls and hands it to a function that turns strings into markup or code. The payload can live entirely in the part of the URL the browser never sends to the server, so the usual server-side defenses never even see it.

What makes it "DOM-based"

In reflected and stored XSS, the server writes your input into the HTML it returns. In DOM-based XSS, the server returns a clean page, and then the page's own JavaScript writes attacker input into the live document. The data flows from a source (where the script reads attacker-controlled input) to a sink (a function or property that executes or renders a string). If untrusted data reaches a dangerous sink without being neutralized, you have DOM XSS.

Sources: where the script reads attacker input

Common sources the page's JavaScript reads from:

location.hash        the part of the URL after #  (never sent to the server)
location.search      the query string
location.href        the whole URL
document.referrer    the page you came from
window.name          survives across navigations
postMessage          data from another window or frame
localStorage / sessionStorage / document.cookie

location.hash is the classic because the fragment is attacker-controlled and the server never receives it, so a bug fed by the hash is invisible to server-side filters and logs.

Sinks: where it executes

A source is only dangerous if it reaches a sink that interprets strings as code or markup:

element.innerHTML / outerHTML        parses a string as HTML
document.write / document.writeln    writes HTML into the document
eval / Function / setTimeout(string) / setInterval(string)   runs a string as JS
element.src / a.href / script.src    javascript: URLs, loading attacker resources
jQuery $(...) / .html() / .append()  $(location.hash) parses HTML

Tracing a source to a sink

The whole task is connecting the two:

// Vulnerable: reads the fragment and writes it as HTML
var msg = decodeURIComponent(location.hash.slice(1));
document.getElementById("out").innerHTML = msg;

Here the source is location.hash and the sink is innerHTML. The fix-free path between them is the bug. A fragment like the following fires:

https://app.example/page#<img src=x onerror=alert(document.domain)>

The server sees only https://app.example/page. Everything after the # stays in the browser and flows straight into innerHTML.

Why server-side encoding does nothing here

Because the dangerous write happens in the browser, after the page loaded. The server never handled your payload, so server-side output encoding, WAFs, and request logging are all bypassed. This is also why DOM XSS often survives long after the reflected and stored bugs on the same app have been fixed, the people patching were looking at the server.

Finding it

Two reliable approaches:

  • Read the JavaScript. Grep the page's scripts for the sinks above, then trace backwards to what feeds them. A source flowing into a sink with no sanitization is the bug.
  • Watch it live. In DevTools, set breakpoints on the sink (or search the sources panel) and change the URL fragment, query string, or referrer. Watch your value arrive at innerHTML or eval.

Note that the context still matters once your data reaches the sink, which overlaps with XSS contexts. A string written into innerHTML needs HTML that executes, while a string passed to eval needs valid JavaScript. When the sink is a client-side template engine rather than a raw DOM call, you are looking at client-side template injection.

DOM XSS rewires how you hunt: stop reading responses and start reading JavaScript. Try it on a free live target.

← Back to Cross-Site Scripting (XSS): The Complete Practical Guide