DOM Clobbering is a type of code-reuse, HTML-only injection attack, where attackers confuse a web application by injecting HTML elements whose id
or name
attribute matches the name of security-sensitive variables or browser APIs, such as variables used for fetching remote content (e.g., script src), and overshadow their value.
It is particularly relevant when script injection is not possible, e.g., when filtered by HTML sanitizers, or mitigated by disallowing or controlling script execution. In these scenarios, attackers may still inject non-script HTML markups into webpages and transform the initially secure markup into executable code, achieving Cross-Site Scripting (XSS).
This cheat sheet is a list of guidelines, secure coding patterns, and practices to prevent or restrict the impact of DOM Clobbering in your web application.
Before we dive into DOM Clobbering, let's refresh our knowledge with some basic Web background.
When a webpage is loaded, the browser creates a DOM tree that represents the structure and content of the page, and JavaScript code has read and write access to this tree.
When creating the DOM tree, browsers also create an attribute for (some) named HTML elements on window
and document
objects. Named HTML elements are those having an id
or name
attribute. For example, the markup:
<form id=x></a>
will lead to browsers creating references to that form element with the attribute x
of window
and document
:
var obj1 = document.getElementById('x');
var obj2 = document.x;
var obj3 = document.x;
var obj4 = window.x;
var obj5 = x; // by default, objects belong to the global Window, so x is same as window.x
console.log(
obj1 === obj2 && obj2 === obj3 &&
obj3 === obj4 && obj4 === obj5
); // true
When accessing an attribute of window
and document
objects, named HTML element references come before lookups of built-in APIs and other attributes on window
and document
that developers have defined, also known as named property accesses. Developers unaware of such behavior may use the content of window/document attributes for sensitive operations, such as URLs for fetching remote content, and attackers can exploit it by injecting markups with colliding names. Similarly to custom attributes/variables, built-in browser APIs may be overshadowed by DOM Clobbering.
If attackers are able to inject (non-script) HTML markup in the DOM tree,
it can change the value of a variable that the web application relies on due to named property accesses, causing it to malfunction, expose sensitive data, or execute attacker-controlled scripts. DOM Clobbering works by taking advantage of this (legacy) behaviour, causing a namespace collision between the execution environment (i.e., window
and document
objects), and JavaScript code.
let redirectTo = window.redirectTo || '/profile/';
location.assign(redirectTo);
The attacker can:
- inject the markup
<a id=redirectTo href='javascript:alert(1)'
and obtain XSS. - inject the markup
<a id=redirectTo href='phishing.com'
and obtain open redirect.
var script = document.createElement('script');
let src = window.config.url || 'script.js';
s.src = src;
document.body.appendChild(s);
The attacker can inject the markup <a id=config><a id=config name=url href='malicious.js'>
to load additional JavaScript code, and obtain arbitrary client-side code execution.
For quick reference, below is the summary of guidelines discussed next.
Guidelines | Description | |
---|---|---|
# 1 | Use HTML Sanitizers | link |
# 2 | Use Content-Security Policy | link |
# 3 | Freeze Sensitive DOM Objects | link |
# 4 | Validate All Inputs to DOM Tree | link |
# 5 | Use Explicit Variable Declarations | link |
# 6 | Do Not Use Document and Window for Global Variables | link |
# 7 | Do Not Trust Document Built-in APIs Before Validation | link |
# 8 | Enforce Type Checking | link |
# 9 | Use Strict Mode | link |
# 10 | Apply Browser Feature Detection | link |
# 11 | Limit Variables to Local Scope | link |
# 12 | Use Unique Variable Names In Production | link |
# 13 | Use Object-oriented Programming Techniques like Encapsulation | link |
Robust HTML sanitizers can prevent or restrict the risk of DOM Clobbering. They can do so in multiple ways. For example:
- completely remove named properties like
id
andname
. While effective, this may hinder the usability when named properties are needed for legitimate functionalties. - namespace isolation, which can be, for example, prefixing the value of named properties by a constant string to limit the risk of naming collisions.
- dynamically checking if named properties of the input mark has collisions with the existing DOM tree, and if that is the case, then remove named properties of the input markup.
OWASP recommends DOMPurify or the Sanitizer API for HTML sanitization.
By default, DOMPurify removes all clobbering collisions with built-in APIs and properties (using the enabled-by-default SANITIZE_DOM
configuration option). ]
To be protected against clobbering of custom variables and properties as well, you need to enable the SANITIZE_NAMED_PROPS
config:
var clean = DOMPurify.sanitize(dirty, {SANITIZE_NAMED_PROPS: true});
This would isolate the namespace of named properties and JavaScript variables by prefixing them with user-content-
string.
The new browser-built-in Sanitizer API does not prevent DOM Clobbering it its default setting, but can be configured to remove named properties:
const sanitizerInstance = new Sanitizer({
blockAttributes: [
{'name': 'id', elements: '*'},
{'name': 'name', elements: '*'}
]
});
containerDOMElement.setHTML(input, {sanitizer: sanitizerInstance});
Content-Security Policy (CSP) is a set of rules that tell the browser which resources are allowed to be loaded on a web page. By restricting the sources of JavaScript files (e.g., with the script-src directive), CSP can prevent malicious code from being injected into the page.
Note: CSP can only mitigate some varints of DOM clobbering attacks, such as when attackers attempt to load new scripts by clobbering script sources, but not when already-present code can be abused for code execution, e.g., clobbering the parameters of code evaluation constructs like eval()
.
A simple way to mitigate DOM Clobbering against individual objects could be to freeze sensitive DOM objects and their properties, e.g., via Object.freeze() method.
Note: Freezing object properties prevents them from being overwritten by named DOM elements. But, determining all objects and object properties that need to be frozen may be not be easy, limiting the usefulness of this approach.
DOM Clobbering can be avoided by defensive programming and adhering to a few coding patterns and guidelines.
Before inserting any markup into the webpage's DOM tree, sanitize id
and name
attributes (see HTML sanitization).
When initializing varibles, always use a variable declarator like var
, let
or const
, which prevents clobbering of the variable.
Note: Declaring a variable with let
does not create a property on window
, unlike var
. Therefore, window.VARNAME
can still be clobbered (assuming VARNAME
is the name of the variable).
Avoid using objects like document
and window
for storing global variables, because they can be easily manipulated. (see, e.g., here).
Document properties, including built-in ones, are always overshadowed by DOM Clobbering, even right after they are assigned a value.
Hint: This is due to the so-called named property visibility algorithm, where named HTML element references come before lookups of built-in APIs and other attributes on document
.
Always check the type of Document and Window properties before using them in sensitive operations, e.g., using the instance of operator.
Hint: When an object is clobbered, it would refer to an HTMLElement instance, which may not be the expected type.
Use strict
mode to prevent unintended global variable creation, and to raise an error when read-only properties are attempted to be over-written.
Instead of relying on browser-specific features or properties, use feature detection to determine whether a feature is supported before using it. This can help prevent errors and DOM Clobberng that might arise when using those features in unsupported browsers.
Hint: Unsupported feature APIs can act as an undefined variable/property in unsupported browsers, making them clobberable.
Global variables are more prone to being overwritten by DOM Clobberng. Whenever possible, use local variables and object properties.
Using unique variable names may help prevent naming collisions that could lead to accidental overwrites.
Encapsulating variables and functions within objects or classes can help prevent them from being overwritten. By making them private, they cannot be accessed from outside the object, making them less prone to DOM Clobbering.