When the Browser Attacks Itself
Reflected and stored XSS need the server to mess up. DOM-based XSS is different - the server might send back perfectly safe HTML, but the JavaScript running in your browser turns innocent data into an attack. It's like hiring a security guard who accidentally lets in burglars.
In DOM-based XSS, the vulnerability exists entirely in client-side code. JavaScript reads user input from somewhere (a "source"), processes it unsafely, and passes it somewhere dangerous (a "sink"). The server never sees the payload - it all happens in the browser.
This is the trickiest XSS type to find because traditional scanning tools that analyze server responses won't detect it. You need to understand JavaScript and analyze client-side code.
Sources and Sinks: The Deadly Combo
DOM-based XSS requires two components:
- Source: Where attacker-controlled data enters the application (URL, cookies, localStorage, postMessage, etc.)
- Sink: Where that data is used in a dangerous way (innerHTML, eval, document.write, etc.)
Common Sources
Dangerous Sinks
Not all sources and sinks are equal. The most dangerous combinations involve user-controlled URL components (especially hash and search) flowing into innerHTML or eval().
The Attack Flow
The hash (#) portion of a URL is NOT sent to the server. This means server-side WAFs and filters are completely blind to DOM XSS payloads delivered via the hash!
Vulnerable Code Patterns
Pattern 1: Direct innerHTML Assignment
Pattern 2: Hash-Based Routing
Pattern 3: Dynamic Script Loading
Pattern 4: eval() Usage
Pattern 5: jQuery Pitfalls
Pattern 6: postMessage
Finding DOM XSS
DOM XSS Hunting Process
1
Identify JavaScript Files
- View page source, find all <script> tags
- Check for inline JavaScript
- Use DevTools Network tab to find loaded .js files
2
Search for Sinks
- Search for: innerHTML, outerHTML, document.write
- Search for: eval, setTimeout, setInterval, Function
- Search for: location.href =, location.assign
- Search for: jQuery.html, $.html
3
Trace Data Flow
- What data reaches each sink?
- Can you control that data via URL, hash, or other sources?
- Is there any sanitization between source and sink?
4
Test the Flow
- Inject a unique string in suspected sources
- Check if it reaches the sink unmodified
- Try XSS payloads appropriate for the sink
Automated Tools
Interactive Practice
Practice Challenges
Knowledge Check
Key Takeaways
- DOM XSS is client-side - the server never sees the payload, so server-side protections don't help
- Sources (location.hash, postMessage, localStorage) flow to sinks (innerHTML, eval, document.write)
- location.hash bypasses WAFs because the fragment is never sent to the server
- Use textContent, not innerHTML when displaying user-controlled text
- Always validate postMessage origins and never trust the message data
- Manual code review is often needed - automated scanners miss many DOM XSS vulnerabilities