← Back to All Vulnerabilities

DOM-based Vulnerabilities

DOM-based vulnerabilities occur when client-side JavaScript dynamically manipulates the Document Object Model (DOM) in an unsafe way, allowing attackers to execute code in the user's browser context.

Understanding DOM-based Vulnerabilities

1. What Are DOM-based Vulnerabilities?

DOM-based vulnerabilities are a type of client-side security issue where the vulnerability exists in the client-side code (JavaScript) rather than in the server-side code. These vulnerabilities occur when JavaScript code takes user-controllable data (like URL parameters, form inputs, or messages from other windows) and uses it in unsafe ways.

2. How DOM-based Attacks Differ from Traditional XSS

Characteristic Reflected/Stored XSS DOM-based XSS
Attack Vector Server-side processing of user input Client-side processing of user input
Server Involvement Server includes malicious content in response Server may never see the malicious payload
Detection Visible in HTTP responses Only visible in client-side execution
Mitigation Server-side input validation and output encoding Client-side input validation and safe DOM manipulation

3. Common Sources of DOM-based Vulnerabilities

4. Dangerous DOM Sinks (Unsafe Operations)

DOM-based Vulnerability Demonstrations

DOM-based XSS via URL Parameters

This demo shows how unsafe handling of URL parameters can lead to DOM-based XSS vulnerabilities.

Search results will appear here...

Current URL:

How This DOM-based XSS Works:

When the vulnerable search function is used:

  1. The search term is added to the URL as a query parameter
  2. JavaScript reads this parameter using location.search
  3. The value is inserted directly into the DOM using innerHTML
  4. Any HTML or JavaScript in the parameter is interpreted and executed

The attack doesn't involve the server at all - it happens entirely in the browser's DOM.

How the Protected Version Works:

The protected search function implements these safeguards:

  1. Uses encodeURIComponent() when adding parameters to URLs
  2. Uses textContent instead of innerHTML to insert text safely
  3. For cases where HTML is needed, it uses proper encoding and sanitization

// Safe DOM manipulation
function displaySafeSearchResults(term) {
    const resultElement = document.getElementById('searchResults');
    const sanitizedTerm = document.createElement('span');
    sanitizedTerm.textContent = term; // Safely handles all characters
    
    resultElement.innerHTML = ''; // Clear previous content
    resultElement.appendChild(document.createTextNode('You searched for: '));
    resultElement.appendChild(sanitizedTerm);
}
                    

DOM-based Open Redirects

This demo shows how improper handling of URL redirections can lead to dangerous DOM-based open redirect vulnerabilities.

Redirection Demo

Imagine you've just logged in and should be redirected to the page you were trying to access:

Redirect blocked for security reasons.

This would redirect to:

How Open Redirect Vulnerabilities Work:

When the vulnerable redirect function is used:

  1. The destination URL is taken directly from user input or URL parameters
  2. This value is assigned to location.href or used in window.open() without validation
  3. If the URL contains a javascript: protocol, it will execute arbitrary JavaScript
  4. Even without JavaScript execution, attackers can redirect users to phishing sites

Open redirects are commonly used in phishing attacks to make malicious URLs appear legitimate or to execute JavaScript.

How to Prevent Open Redirect Vulnerabilities:

  1. Allowlist Valid Destinations - Only allow redirects to known, trusted destinations
  2. Use Relative URLs - Prefer relative paths to prevent redirects to external domains
  3. Validate URL Protocol - Explicitly check that the protocol is HTTP or HTTPS
  4. Indirect References - Use indirect references (e.g., "destination=1" maps to "/dashboard")

// Safe redirect function
function safeRedirect(url) {
    // Only allow relative URLs or specific domains
    if (url.startsWith('/') || url.startsWith('https://trusted-domain.com')) {
        // Additional validation for relative URLs
        if (url.startsWith('/')) {
            // Make sure there's no "/../" path traversal
            if (!/\/\.\.\//.test(url)) {
                location.href = url;
                return true;
            }
        } else {
            location.href = url;
            return true;
        }
    }
    
    // Block the redirect and show an error
    console.error("Redirect blocked: Invalid destination");
    return false;
}
                    

DOM-based Vulnerabilities in postMessage

This demo shows how improper validation of postMessage data can lead to DOM-based vulnerabilities.

Cross-Window Communication Demo

This simulates communication between a parent page and an iframe:

Child Frame (Simulated)

No messages received yet.

How postMessage Vulnerabilities Work:

When the vulnerable postMessage handler is used:

  1. Messages are sent between windows/frames using window.postMessage()
  2. The receiving window doesn't validate the message origin or structure
  3. Message content is processed and inserted into the DOM without sanitization
  4. This allows HTML injection or unsafe JavaScript execution

postMessage vulnerabilities are particularly dangerous in complex web applications that use iframes or third-party integrations.

How to Secure postMessage Communications:

  1. Always Validate Origin - Check the message's origin against an allowlist
  2. Validate Message Structure - Ensure the message has the expected format and properties
  3. Sanitize Content - Never insert HTML from messages without proper sanitization
  4. Use Message Types - Implement a message type system to control how data is processed

// Safe message event listener
window.addEventListener('message', function(event) {
    // 1. Validate the origin
    if (!/^https:\/\/(trusted-domain\.com|app\.trusted-domain\.com)$/.test(event.origin)) {
        console.error('Message from untrusted origin rejected:', event.origin);
        return;
    }
    
    // 2. Validate message structure
    try {
        const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
        
        if (!data || typeof data !== 'object' || !data.type) {
            console.error('Invalid message format');
            return;
        }
        
        // 3. Process based on validated message type
        switch (data.type) {
            case 'TEXT_MESSAGE':
                if (typeof data.text !== 'string') return;
                displayTextMessage(data.text); // Uses textContent, not innerHTML
                break;
            
            case 'UI_UPDATE':
                if (!data.elementId || typeof data.elementId !== 'string') return;
                if (!data.property || typeof data.property !== 'string') return;
                if (data.value === undefined) return;
                
                // Only allow updates to specific elements and properties
                const allowedUpdates = {
                    'status-display': ['textContent', 'className'],
                    'progress-bar': ['style.width', 'aria-valuenow']
                };
                
                if (!allowedUpdates[data.elementId] || 
                    !allowedUpdates[data.elementId].includes(data.property)) {
                    console.error('Unauthorized element or property update');
                    return;
                }
                
                updateElement(data.elementId, data.property, data.value);
                break;
                
            default:
                console.error('Unknown message type:', data.type);
        }
    } catch (e) {
        console.error('Error processing message:', e);
    }
});
                    

DOM-based Cookie Manipulation

This demo shows how improper handling of cookies in JavaScript can lead to DOM-based vulnerabilities.

User Preferences

Theme Preview

This text will show your selected theme styling.

Current cookies:

How Cookie Manipulation Vulnerabilities Work:

When the vulnerable cookie handling code is used:

  1. User input is stored directly in a cookie without proper validation or encoding
  2. When the cookie is read, its value is used in DOM operations without sanitization
  3. This allows attackers to inject JavaScript or HTML that executes when the cookie is processed
  4. The attack persists across page loads because the payload is stored in the cookie

Cookie manipulation differs from other DOM-based attacks because the payload is stored client-side and persists across sessions until the cookie expires or is deleted.

How to Secure Cookie Handling:

  1. Validate and Encode Cookie Values - Strictly validate inputs and encode special characters
  2. Use JSON Serialization - Properly encode/decode structured data in cookies
  3. Set Secure Flags - Use HttpOnly, Secure, and SameSite flags for sensitive cookies
  4. Safe DOM Manipulation - Never use cookie values directly in innerHTML or JavaScript execution contexts

// Safe cookie handling
function setSecureCookie(name, value, options = {}) {
    // Validate the name and value
    if (!/^[a-zA-Z0-9_]+$/.test(name)) {
        throw new Error('Invalid cookie name');
    }
    
    // For complex values, use JSON with proper encoding
    if (typeof value === 'object') {
        value = JSON.stringify(value);
    }
    
    // Encode the value to prevent breaking the cookie format or injection
    const encodedValue = encodeURIComponent(value);
    
    // Set default secure options
    const defaultOptions = {
        path: '/',
        sameSite: 'strict',
        secure: location.protocol === 'https:',
        // Note: HttpOnly can only be set by the server
    };
    
    const cookieOptions = { ...defaultOptions, ...options };
    let cookieString = `${name}=${encodedValue}`;
    
    // Add the options to the cookie string
    if (cookieOptions.path) cookieString += `; path=${cookieOptions.path}`;
    if (cookieOptions.domain) cookieString += `; domain=${cookieOptions.domain}`;
    if (cookieOptions.maxAge) cookieString += `; max-age=${cookieOptions.maxAge}`;
    if (cookieOptions.expires) cookieString += `; expires=${cookieOptions.expires.toUTCString()}`;
    if (cookieOptions.sameSite) cookieString += `; samesite=${cookieOptions.sameSite}`;
    if (cookieOptions.secure) cookieString += `; secure`;
    
    // Set the cookie
    document.cookie = cookieString;
}
                    

How to Prevent DOM-based Vulnerabilities

1. Use Safe DOM Methods and Properties

Instead of vulnerable methods, use safer alternatives:

// Unsafe way
element.innerHTML = userControlledValue;

// Safe way
element.textContent = userControlledValue;

// When HTML is needed:
const sanitizedHTML = DOMPurify.sanitize(userControlledValue);
element.innerHTML = sanitizedHTML;
        

2. Use HTML Sanitization Libraries

When you need to set HTML content, use a sanitization library like DOMPurify:

// Include the library
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.3.3/purify.min.js"></script>

// Use it to sanitize HTML before insertion
const userHTML = getParameterFromUrl('content');
const clean = DOMPurify.sanitize(userHTML, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'ol', 'li'],
    ALLOWED_ATTR: ['href', 'target']
});
element.innerHTML = clean;
        

3. Implement Input Validation

Always validate user input on the client side (in addition to server-side validation):

// Validate URL parameters before use
function getValidatedParameter(param, pattern) {
    const value = new URLSearchParams(window.location.search).get(param);
    
    if (!value) return null;
    
    // Check against a whitelist pattern
    if (pattern && !pattern.test(value)) {
        console.error(`Invalid value for parameter ${param}`);
        return null;
    }
    
    return value;
}

// Example usage
const userId = getValidatedParameter('userId', /^[0-9]{1,10}$/);
if (userId) {
    loadUserProfile(userId);
}
        

4. Apply URL Encoding/Decoding Correctly

Use proper URL encoding/decoding functions:

// When adding parameters to URLs
const params = new URLSearchParams();
params.append('search', userInput); // Automatically handles encoding

// Creating a URL with parameters
const url = new URL('/search', window.location.origin);
url.searchParams.append('term', userInput);
history.pushState({}, '', url);

// When extracting URL parameters
const urlParams = new URLSearchParams(window.location.search);
const term = urlParams.get('term'); // Automatically handles decoding
        

5. Implement Content Security Policy (CSP)

Use CSP to restrict script execution and prevent many DOM-based attacks:

// Add this header to your server responses
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'
        

6. Secure postMessage Communication

Always validate the origin and message structure:

// Sending messages
targetWindow.postMessage({
    type: 'USER_ACTION',
    action: 'save',
    data: { id: 123, name: 'Example' }
}, 'https://trusted-target.com');

// Receiving messages
window.addEventListener('message', function(event) {
    // Always verify the origin
    if (event.origin !== 'https://trusted-source.com') {
        console.error('Message from untrusted origin:', event.origin);
        return;
    }
    
    // Validate the message structure
    try {
        const data = typeof event.data === 'object' ? event.data : JSON.parse(event.data);
        
        if (!data.type || typeof data.type !== 'string') {
            throw new Error('Invalid message format');
        }
        
        // Process message based on type
        switch (data.type) {
            case 'USER_ACTION':
                handleUserAction(data);
                break;
            // Other cases
            default:
                console.error('Unknown message type');
        }
    } catch (e) {
        console.error('Invalid message:', e);
    }
});
        

7. Use JavaScript Frameworks Securely

Follow security best practices for your framework: