DOM Clobbering and Global Variable Lookups
DOM clobbering is a technique where an attacker injects
benign-looking HTML elements into a webpage to override or “clobber”
global JavaScript variables, namespaces, and object properties. This
vulnerability stems from legacy browser features that automatically
create global references for elements with specific attributes like
id and name. By manipulating the global
variable lookup process, an attacker can influence application logic,
bypass sanitization controls, or escalate non-executable HTML injection
into full Cross-Site Scripting (XSS).
The Mechanics of Global Lookups in the Browser
When a script references a variable without declaring it explicitly
via let, const, or var, the
JavaScript engine searches the scope chain until it reaches the global
object, which in web browsers is window.
Browsers maintain backward compatibility with legacy web standards by
implementing the Window interface’s named property access.
According to the HTML specification, if a window property is not already
defined, the browser will resolve variable lookups by checking the DOM
for:
- Any HTML element with an
idattribute matching the variable name. - Form-associated elements (such as
<embed>,<form>,<img>, or<object>) with anameattribute matching the variable name.
If a match is found, the browser returns the corresponding
HTMLElement or an HTMLCollection instead of
undefined.
How Clobbering Overwrites Global Variables
Consider an application that checks for a global configuration variable or an uninitialized fallback before executing code:
// Application Code
let apiConfig = window.apiConfig || { url: "/api/v1/data" };
fetch(apiConfig.url);If an attacker can inject HTML into the page, they can insert:
<a id="apiConfig" href="https://attacker.com/malicious-endpoint"></a>When window.apiConfig is evaluated: 1. The browser finds
no explicit JavaScript property defined on window. 2. The
browser falls back to the DOM lookup and resolves apiConfig
to the HTMLAnchorElement. 3. Because the anchor element is
truthy, the fallback { url: "/api/v1/data" } is ignored. 4.
When apiConfig.url (or apiConfig.href) is
read, it accesses the properties of the injected DOM node, returning the
attacker-controlled URL.
Deep Property Clobbering
DOM clobbering is not restricted to single-level global variables;
attackers can also clobber two-level object paths (e.g.,
window.config.url) using element nesting behaviors.
Browsers expose children of <form> elements as
properties on the form object itself if the children have a
name attribute. For example:
<form id="config">
<input name="url" value="https://attacker.com/payload.js">
</form>In this scenario: - window.config resolves to the
HTMLFormElement (due to id="config"). -
window.config.url resolves to the
HTMLInputElement (due to name="url" on the
child). - In string contexts, or via property reflection like
getAttribute('value'), the application reads the
manipulated data.
To clobber native toString conversions, attackers
frequently use <a> elements because the
HTMLAnchorElement implements a custom
toString() method that returns its href
attribute:
<a id="target" href="javascript:alert(1)"></a>Evaluating String(window.target) or concatenating
window.target into a string context produces
"javascript:alert(1)".
Defensive Measures
Securing applications against DOM clobbering requires eliminating reliance on ambient global lookups and securing object structures:
- Explicit Variable Initialization: Always declare
variables using
constorlet. Explicitly initialize default values rather than relying on dynamic lookups on the global scope. - Strict Property Checks: Avoid checking global
presence with implicit truthiness (e.g.,
if (window.feature)). UseObject.prototype.hasOwnProperty.call(window, 'feature')or checktypeof window.feature !== 'undefined' && !(window.feature instanceof HTMLElement). - HTML Sanitization: Configure HTML sanitizers (such
as DOMPurify) with strict rules to strip dangerous
idandnameattributes from user-supplied markup. - Object Freezing: Use
Object.freeze()orObject.seal()on global configuration objects before any untrusted DOM content is parsed or injected.