DOM Clobbering

This article first appeared on the HTMLHell Advent Calendar 2022.

Motivation

When thinking of HTML-related security bugs, people often think of script injection attacks, which is also known as Cross-Site Scripting (XSS). If an attacker is able to submit, modify or store content on your web page, they might include evil JavaScript code to modify the page or steal user information like cookies. Most developers out there protect their websites against XSS by disallowing or controlling script execution.

However, this article is NOT about XSS.

This article is about an interesting attack that relies on HTML-only injections. The techniques here were first described by Gareth Heyes in 2013, the topic gained additional attention due to a recent research paper and its accompanying website https://domclob.xyz/ from Khodayari et al.

Background

Before we dive into the vulnerability class of DOM Clobbering, we need to establish some background.

Have you ever noticed that whenever you include a HTML element with an id attribute, it becomes available as a global name (and a name on the window object)? So, for example

<form id=freddy>

…will lead to window.freddy becoming a reference to that form element.

This is a legacy inheritance from the very old days of HTML and also known as named property access in the HTML spec. Studying the spec will inform us that the following elements will also appear on the document object: embed, form, img, and object.

Given that web browsers have to maintain backwards compatibility support for relatively old web pages, named property access is designed in such a way that element references come before lookups of APIs and other new attributes on the window and document object.

DOM Clobbering Attacks

In essence, this means that an attacker with the ability to inject HTML may mess with simple Web APIs - like the property document.hidden:

Imagine your website is trying to reduce or stop animations based on page visibility like so:

if (!document.hidden) {
  startAnimations(); 
}

an injected HTML form element will be able to make this truthy, regardless of whether the page is really hidden:

With <form id=hidden> the value document.hidden would return a HTMLFormElement and therefore be considered truthy.

So, in essence, DOM Clobbering is an attack where injected HTML may confuse the existing JavaScript application and therefore change the application logic.

Another typical example of this vulnerability is where code is trying to fall back to a default config if some variable is set, like so:

let config = defaultConfig || { /* some config here */};

If the defaultConfig name is clobbered, that other config is never going to be assigned correctly!

Let's look at a third, more complicated example: In this case, an attacker has injected two input elements with a id attribute of childNodes into an existing form f like here:

<form id=f>
  <input id=childNodes>foo
  <input id=childNodes>bar
  <input type=text value="hidden-from-js">
</form>

These injected input elements will overwrite the form element's property and all following JavaScript code will not be able to iterate over f.childNodes!

Compare the clobbered property with the actual children in this screenshot below:

Screenshot comparing devtools results for childNodes versus children property

Do you see the difference in length?

An attack vector like this one could be used to fool or confuse the website's JavaScript code when iterating over attacker-controlled DOM trees.

Just imagine what else could be overridden!

How would your web page behave if seemingly innocent function calls like document.getElementById() can fail with "Uncaught TypeError: document.getElementById is not a function" because the API has also been clobbered

Prevention

The best way to prevent DOM Clobbering is to use a strong Sanitizer library. A sanitizer typically goes through user-supplied HTML and only leaves the harmless bits - removing scripts, event handlers and other injections - like DOM Clobbering.

The author of this article recommends DOMPurify. By default, DOMPurify will remove all clobbering properties. Using the option SANITIZE_NAMED_PROPS: true will instead prefix user-supplied identifiers with user-content-.

However, if you want to live on the edge you might also want to look at the upcoming Sanitizer API. The specification for the Sanitizer API is still in development, but Chrome and Firefox are already shipping a prototype and the Sanitizer API Playground has instructions on how to enable it.

In order to fix DOM Clobbering with the Sanitizer API, you need to configure the Sanitizer to disallow attributes of type name and id:

const mySanitizer = new Sanitizer({
  blockAttributes: [
    {'name': 'id', elements: '*'},
    {'name': 'name', elements: '*'}
  ]
});
someElement.setHTML(input, {sanitizer: mySanitizer});

Good luck!


If you find a mistake in this article, you can submit a pull request on GitHub.

Other posts

  1. How Firefox gives special permissions to some domains (Fri 02 February 2024)
  2. Examine Firefox Inter-Process Communication using JavaScript in 2023 (Mon 17 April 2023)
  3. Origins, Sites and other Terminologies (Sat 14 January 2023)
  4. Finding and Fixing DOM-based XSS with Static Analysis (Mon 02 January 2023)
  5. DOM Clobbering (Mon 12 December 2022)
  6. Neue Methoden für Cross-Origin Isolation: Resource, Opener & Embedding Policies mit COOP, COEP, CORP und CORB (Thu 10 November 2022)
  7. Reference Sheet for Principals in Mozilla Code (Mon 03 August 2020)
  8. Hardening Firefox against Injection Attacks – The Technical Details (Tue 07 July 2020)
  9. Understanding Web Security Checks in Firefox (Part 1) (Wed 10 June 2020)
  10. Help Test Firefox's built-in HTML Sanitizer to protect against UXSS bugs (Fri 06 December 2019)
  11. Remote Code Execution in Firefox beyond memory corruptions (Sun 29 September 2019)
  12. XSS in The Digital #ClimateStrike Widget (Mon 23 September 2019)
  13. Chrome switching the XSSAuditor to filter mode re-enables old attack (Fri 10 May 2019)
  14. Challenge Write-up: Subresource Integrity in Service Workers (Sat 25 March 2017)
  15. Finding the SqueezeBox Radio Default SSH Password (Fri 02 September 2016)
  16. New CSP directive to make Subresource Integrity mandatory (`require-sri-for`) (Thu 02 June 2016)
  17. Firefox OS apps and beyond (Tue 12 April 2016)
  18. Teacher's Pinboard Write-up (Wed 02 December 2015)
  19. A CDN that can not XSS you: Using Subresource Integrity (Sun 19 July 2015)
  20. The Twitter Gazebo (Sat 18 July 2015)
  21. German Firefox 1.0 ad (OCR) (Sun 09 November 2014)
  22. My thoughts on Tor appliances (Tue 14 October 2014)
  23. Subresource Integrity (Sun 05 October 2014)
  24. Revoke App Permissions on Firefox OS (Sun 24 August 2014)
  25. (Self) XSS at Mozilla's internal Phonebook (Fri 23 May 2014)
  26. Tales of Python's Encoding (Mon 17 March 2014)
  27. On the X-Frame-Options Security Header (Thu 12 December 2013)
  28. html2dom (Tue 24 September 2013)
  29. Security Review: HTML sanitizer in Thunderbird (Mon 22 July 2013)
  30. Week 29 2013 (Sun 21 July 2013)
  31. The First Post (Tue 16 July 2013)