How DOM Clobbering Overrides Global Variables
DOM clobbering is a browser behavior and security vulnerability where
HTML elements with specific id or name
attributes inject properties into the global window object
or document namespace. When web applications rely on global
variables or object configurations without strict initialization,
attackers can inject seemingly harmless HTML markup to overwrite these
variables. This article explains the browser mechanics behind DOM
clobbering, how JavaScript resolves named elements as globals, how
nested clobbering works, and how to defend against it.
The Browser Legacy: Window Object Property Resolution
To maintain backwards compatibility with legacy web standards, modern
browsers automatically map HTML elements containing id or
name attributes directly to properties on the global
window object.
For example, consider the following HTML markup:
<form id="config"></form>When this element is rendered, the browser automatically creates a
property on window:
console.log(window.config); // Returns [object HTMLFormElement]
console.log(config); // Also references the same element directlyBecause window is the global execution context in
client-side JavaScript, any variable reference that is not declared in
the local scope via var, let, or
const falls back to checking the properties of
window.
How Global Variable Overriding Occurs
DOM clobbering occurs when an application checks for the existence of
a global configuration object or property that has not yet been
explicitly assigned in JavaScript, or when the application accesses
properties directly from window.
Consider a vulnerable script pattern:
// The application checks for an existing config object
var apiEndpoint = window.config ? window.config.url : "/default/api";If an attacker injects HTML prior to this script’s execution:
<a id="config" href="https://attacker.com/malicious-api"></a>The browser maps the <a> element to
window.config. Because the HTML anchor tag has a built-in
href property that converts to a string representation via
.url or .href, accessing
window.config evaluates to the DOM element instead of
undefined. Consequently, the application reads the
attacker-supplied URL instead of falling back to the default path.
Multi-Level (Nested) Clobbering
Attackers are not limited to single-level global properties. By
combining elements such as <form> tags with named
children, or using collections of elements sharing the same name,
attackers can clobber multi-level objects like
window.user.profile.
1. Form Child Relationship
Form elements automatically expose their child inputs as properties
using their name attributes:
<form id="user">
<input name="role" value="admin">
</form>In JavaScript, evaluating window.user.role.value will
yield the string "admin", overriding an uninitialized
user object.
2. Grouped Elements (HTMLCollection)
If multiple elements share the same id or
name, the browser groups them into an
HTMLCollection:
<a id="user" name="profile" href="https://evil.com/payload.js"></a>
<a id="user"></a>Here, window.user resolves to an
HTMLCollection, and window.user.profile
accesses the first link’s attributes.
Why JavaScript Scope Does Not Always Protect
JavaScript scoping rules determine what gets clobbered:
- Explicit Declarations (
let,const,var): If a variable is declared in scope, it takes precedence over the DOM element with the same identifier. - Property Lookups on
window: Accessingwindow.variableNamedirectly bypasses lexical scoping and directly retrieves the clobbered DOM element if no JavaScript property was explicitly defined onwindow. - Undeclared Variables: Looking up an undeclared identifier traverses the scope chain up to the global object, falling back to the DOM element.
Preventing DOM Clobbering
Use Strict Variable Declarations: Always declare variables using
constorletto prevent uninitialized identifiers from falling back towindow.Explicit Type and Instance Checking: Verify that properties are instances of the expected object types rather than relying solely on truthy checks:
if (window.config && !(window.config instanceof HTMLElement)) { // Safe to use config }HTML Sanitization: When parsing user-controlled markup, use sanitization libraries configured to strip or neutralize
idandnameattributes that could interfere with application logic.Use
Object.freeze()or Explicit Initialization: Define global configuration namespaces explicitly in an immutable format before rendering dynamic or user-generated HTML.