Overview
DOM-based Cross-Site Scripting (DOM XSS) is a variant of XSS in which the vulnerability exists entirely within client-side JavaScript code rather than in server-generated HTML. The attack payload never travels to the server; instead, the browser's own JavaScript reads attacker-controlled data from a source and passes it, without adequate sanitisation, to a dangerous sink that causes script execution. Because the server never sees the payload, server-side input validation and output encoding cannot prevent the attack on their own.
DOM XSS is catalogued under CWE-79 alongside reflected and stored XSS, but its purely client-side data flow makes it distinct in both detection and remediation strategies. Sensagraph detects DOM XSS by analysing client-side JavaScript data flows between known sources and dangerous sinks.
How it works
The attack chain consists of two components: a source (where attacker-controlled data enters JavaScript) and a sink (a DOM API or property that causes code execution or HTML injection when passed unsanitised data).
Common Sources
location.hash— URL fragment, never sent to the serverlocation.search— query string parameterslocation.href,location.pathnamedocument.referrerwindow.namepostMessageevent datalocalStorage/sessionStoragevalues written by a compromised origin- WebSocket messages, XMLHttpRequest / Fetch responses containing attacker-influenced data
Common Dangerous Sinks
element.innerHTML,element.outerHTMLdocument.write(),document.writeln()eval(),setTimeout(string),setInterval(string),new Function(string)element.src,element.href(when set tojavascript:URIs)location.href,location.assign(),location.replace()(open-redirect escalation to XSS)- jQuery methods:
$()with HTML strings,.html(),.append()
Attack Example
Consider the following vulnerable JavaScript snippet:
// Vulnerable code
const name = decodeURIComponent(location.hash.slice(1));
document.getElementById('greeting').innerHTML = 'Hello, ' + name;
An attacker crafts the URL https://example.com/page#<img src=x onerror=alert(document.cookie)> and delivers it to a victim. When the victim's browser loads the page, the fragment value is read directly into innerHTML, causing the injected script to execute in the context of example.com — without the payload ever appearing in a server request.
Why Server-Side Defences Are Insufficient
- The URL fragment (
#...) is not transmitted to the server per the HTTP specification (RFC 3986 §3.5). - Server-side sanitisation and WAFs operate on HTTP request data they never receive.
- Content Security Policy (CSP) can limit impact but cannot fully prevent DOM XSS when
unsafe-evalorunsafe-inlineis present, or when the sink does not trigger inline script execution.
Business impact
A successful DOM XSS exploit gives an attacker arbitrary JavaScript execution in the victim's browser under the target origin. Potential consequences include:
- Session hijacking — theft of session cookies (where
HttpOnlyis absent) or token-based credentials stored in JavaScript-accessible storage. - Credential harvesting — injection of fake login forms or keyloggers onto trusted pages.
- Account takeover — performing authenticated actions on behalf of the victim (CSRF-equivalent via script).
- Data exfiltration — reading sensitive page content, DOM state, or accessible APIs and exfiltrating the data to an attacker-controlled endpoint.
- Malware distribution — redirecting victims to exploit kits or initiating drive-by downloads.
- Regulatory exposure — breaches resulting from XSS may trigger GDPR, PCI DSS, or HIPAA notification obligations and fines.
Because DOM XSS often targets the URL fragment and leaves no server-side log trace, forensic investigation and incident response are more complex than for server-side XSS variants.
How to fix it
- Avoid dangerous sinks entirely where possible. Prefer DOM APIs that do not parse HTML: use
element.textContentinstead ofelement.innerHTMLwhen inserting plain text. Usedocument.createElement()andappendChild()to build DOM trees programmatically. - Sanitise all data before writing to HTML sinks. If HTML output is required, use a trusted, actively maintained client-side sanitisation library such as DOMPurify. Always configure it restrictively (e.g., forbid
javascript:URIs).// Safe use of DOMPurify const name = decodeURIComponent(location.hash.slice(1)); document.getElementById('greeting').innerHTML = 'Hello, ' + DOMPurify.sanitize(name); - Apply context-aware output encoding. Encode data for the specific context it is being inserted into (HTML body, HTML attribute, JavaScript string, CSS, URL). Libraries such as the OWASP Java Encoder (server-side) or DOMPurify / he.js (client-side) provide context-aware encoding.
- Avoid passing attacker-controlled strings to JavaScript execution sinks. Never pass user-supplied data to
eval(),setTimeout/setIntervalwith string arguments,new Function(), ordocument.write(). - Implement a strict Content Security Policy (CSP). A CSP with
script-src 'nonce-{random}'orscript-src 'strict-dynamic'eliminates most inline-script-based exploitation even if a DOM XSS flaw exists. Removeunsafe-inlineandunsafe-evaldirectives. - Validate and restrict
postMessageorigins. Always verifyevent.originagainst an allowlist before processingpostMessagedata. Never passpostMessagedata directly to a dangerous sink. - Audit JavaScript frameworks and third-party libraries. Many frameworks (React, Angular, Vue) provide safe-by-default data binding but expose escape hatches (
dangerouslySetInnerHTML,bypassSecurityTrust*,v-html) that re-introduce DOM XSS risk if misused. Audit all usages. - Conduct code review and automated SAST/DAST scanning. Use static analysis tools capable of taint tracking across JavaScript data flows to identify source-to-sink paths. Dynamic testing should include fragment-based and
postMessage-based payloads. - Adopt Trusted Types (where supported). The Trusted Types browser API (enforced via CSP
require-trusted-types-for 'script') mandates that values written to dangerous DOM sinks pass through a defined policy, making DOM XSS injection significantly harder.
References
- OWASP – DOM Based XSS
- OWASP Top 10 2021 – A03: Injection
- OWASP – DOM-based XSS Prevention Cheat Sheet
- OWASP – XSS Prevention Cheat Sheet
- MITRE CWE-79 – Improper Neutralization of Input During Web Page Generation
- PortSwigger Web Security Academy – DOM-based XSS
- MDN Web Docs – innerHTML Security Considerations
- W3C – Trusted Types Specification
- MDN Web Docs – Content-Security-Policy
- RFC 3986 §3.5 – URI Fragment Identifier
- DOMPurify – Trusted HTML Sanitisation Library