Overview
DOM-based Cross-Site Scripting (XSS) is a type of XSS vulnerability where the attack payload is executed as a result of modifying the DOM environment in the victim’s browser. Unlike Reflected and Stored XSS, the malicious payload may never reach the server - the vulnerability exists entirely in the client-side JavaScript code. Key Characteristic: The vulnerability is in client-side JavaScript that processes user-controlled data (often from the URL) and writes it to the DOM without proper sanitization.How DOM-based XSS Differs from Other XSS Types
| Feature | DOM-based XSS | Reflected XSS | Stored XSS |
|---|---|---|---|
| Execution Location | Client-side only | Server reflects to client | Server stores, retrieves, sends to client |
| Server Processing | May not reach server | Processed by server | Stored by server |
| Payload Location | URL (often fragment #) | URL query parameters | Database |
| Detection Difficulty | Hardest (client-side) | Moderate | Easier (in database) |
| WAF Protection | Limited (bypass server) | Effective | Effective |
| Code Review Target | JavaScript | Server-side code | Server-side code |
| Attack Vector | Malicious URL with fragment | Malicious URL | Direct input to app |
- The server response might be completely safe
- The vulnerability is in how JavaScript handles the URL
- URL fragments (
#hash) never get sent to the server - Traditional server-side XSS filters won’t catch it
- Requires JavaScript code review, not just server code
Vulnerability Objective
Goal: Run your own JavaScript in another user’s browser to steal the cookie of a logged-in user, leveraging client-side DOM manipulation.Application Context
DVWA’s DOM XSS module implements a language selector that uses JavaScript to read from the URL and dynamically populate a dropdown menu. The Vulnerable JavaScript (xss_d/index.php:50-61):
- Basic Alert:
- Cookie Theft:
- Cookie Exfiltration:
<script, the server redirects to a safe default.
Vulnerability:
- The check is case-insensitive (
stripos) but only checks for<script - HTML event handlers are not blocked
- The client-side JavaScript still has the vulnerability
- Break Out of the Select Tag (Primary Method):
English</option></select>closes the current option and select tags<img src=x onerror=alert(1)>injects a new image tag with JavaScript event- No
<scripttag, so bypasses server filter
- SVG with onload:
- Body Tag:
?default=), but URL fragments (#hash) are processed by JavaScript but never sent to the server.
Bypass Using URL Fragment:
- Server sees
?default=English(valid, allowed) - Browser loads the page normally
- JavaScript reads full URL including fragment:
English#<script>alert(1)</script> - The
document.location.href.indexOf("default=")still finds the parameter - Extracts everything after
default=, including the fragment - Injects and executes the script
Impossible Security (Secure Implementation)
The impossible level fixes the vulnerability by removingdecodeURI() and relying on browser auto-encoding.
Server-side Code (xss_d/source/impossible.php):
xss_d/index.php:34-38):
- Modern browsers automatically encode special characters in URLs
<becomes%3C>becomes%3E- Without
decodeURI(), these stay encoded %3Cscript%3Eis rendered as text, not executed
- Attacker creates URL: http://dvwa.local/xss_d/?default=English#
- WAF inspects request to server: GET /xss_d/?default=English ✓ Passes WAF (English is valid)
- Browser receives clean response
-
JavaScript executes:
- Reads full URL (including #fragment)
- Extracts and injects payload
- Executes attacker’s script
- Cookie sent to attacker without server ever seeing the payload
Dangerous JavaScript Patterns
1. Unsafe DOM Manipulation
Vulnerable:2. Dangerous Functions
These functions can execute code from strings:2. Sanitize User Input
- Inline scripts (including injected ones)
eval()and similar functions- Scripts from external domains
4. Avoid URL Fragments for Data
Testing for DOM XSS
Manual Testing Steps
-
Identify Sources (where user input comes from):
document.location.*window.location.*document.URLdocument.referrerwindow.namepostMessagedata
-
Identify Sinks (dangerous functions):
document.write()element.innerHTMLeval()setTimeout()/setInterval()with stringslocation.href = userInput
- Test Payloads:
