You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/pentesting-web/browser-extension-pentesting-methodology/README.md
+67Lines changed: 67 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -375,6 +375,69 @@ The **less extensions and URLs** indicated here, the **smaller the attack surfac
375
375
>
376
376
> Moreover, if the client installs a rouge extension, even if it isn't allowed to communicate with the vulnerable extension, it could inject **XSS data in an allowed web page** or abuse **`WebRequest`** or **`DeclarativeNetRequest`** APIs to manipulate requests on a targeted domain altering a page's request for a **JavaScript file**. (Note that CSP on the targeted page could prevent these attacks). This idea comes [**from this writeup**](https://www.darkrelay.com/post/opera-zero-day-rce-vulnerability).
377
377
378
+
#### Wildcard-trusted web origins to privileged action injection
379
+
380
+
If an extension exposes a **high-privilege message handler** to the web via `externally_connectable`, avoid trusting a broad pattern such as `https://*.example.com/*`. A single **XSS**, **subdomain takeover**, or **vendor widget compromise** on any matching subdomain becomes equivalent to owning the extension's web-facing API.
381
+
382
+
Typical exploitation path:
383
+
384
+
1. Find the extension ID and enumerate `externally_connectable.matches`.
385
+
2. Identify a message type that triggers a **privileged action** (`open tab`, `read page`, `submit prompt`, `fetch with extension privileges`, `native messaging`, and so on).
386
+
3. Get JavaScript execution in **any** trusted origin.
387
+
4. Send the request directly to the extension:
388
+
389
+
```javascript
390
+
chrome.runtime.sendMessage("<extension-id>", {
391
+
type: "privileged_action",
392
+
payload: { attacker: "controlled" },
393
+
})
394
+
```
395
+
396
+
This is especially dangerous in **agentic extensions** or assistants because the "message" might be a full **instruction prompt** that is executed with the extension's host permissions.
397
+
398
+
> [!CAUTION]
399
+
> Treat **vendor code hosted on a first-party trusted subdomain** as part of the trust boundary. If `captcha.example.com`, `cdn.example.com`, or a support widget origin is allowed by `externally_connectable`, an XSS in that third-party component can pivot into the extension exactly as if the bug existed on the main application.
When reviewing a browser extension that accepts messages from web pages:
404
+
405
+
- Search for `chrome.runtime.onMessageExternal.addListener`, `chrome.runtime.onConnectExternal.addListener`, and sensitive handlers reachable from those listeners.
406
+
- Verify the extension performs **exact origin equality** checks on the sender instead of suffix, regex, or wildcard matching.
407
+
- Verify the handler authorizes **message type + origin** together. A safe origin for telemetry or onboarding is not automatically safe for privileged actions.
408
+
- Confirm the extension does not trust a subdomain just because it shares the parent eTLD+1.
409
+
- Check whether any allowed origin hosts **third-party JavaScript**, **user-generated content**, or **legacy static assets**.
410
+
411
+
Bad patterns:
412
+
413
+
```javascript
414
+
if (sender.origin.endsWith(".example.com")) { /* trust */ }
415
+
if (/^https:\/\/.*\.example\.com$/.test(sender.origin)) { /* trust */ }
416
+
```
417
+
418
+
Prefer strict matching to the minimal set of origins:
419
+
420
+
```javascript
421
+
if (sender.origin!=="https://app.example.com") return
422
+
```
423
+
424
+
#### Rollback hunting on trusted static assets
425
+
426
+
If the trusted origin serves **versioned static assets** or embedded widgets from predictable paths, try walking older versions looking for still-reachable vulnerable builds. This is useful when the current version is patched but the extension still trusts the origin hosting archived assets.
427
+
428
+
Common patterns:
429
+
430
+
-`/assets/widget/1.26.0/index.html`
431
+
-`/static/app-2024.12.1/`
432
+
-`/cdn/component/v1234/`
433
+
434
+
Practical checks:
435
+
436
+
- Start from a version observed in network traffic or page source.
- Look for `200`, `301`, or cache hits on old builds.
439
+
- Review older JS bundles for DOM XSS, unsafe message handlers, or gadget endpoints that can re-establish JavaScript execution on the trusted origin.
440
+
378
441
## Communication summary
379
442
380
443
### Extension <--> WebApp
@@ -743,6 +806,8 @@ Even though Browser Extensions have a **limited attack surface**, some of them m
743
806
- [ ] Use a **strong****`content_security_policy`**
744
807
- [ ] **Limit** as much as possible the **`externally_connectable`**, if none is needed and possible, do not leave it by default, specify **`{}`**
745
808
- [ ] If **URL vulnerable to XSS or to takeover** is mentioned here, an attacker will be able to **send messages to the background scripts directly**. Very powerful bypass.
809
+
- [ ] Reject wildcard or suffix trust such as `*.example.com`for**privileged** external message handlers.
810
+
- [ ] Review whether any allowed origin serves **vendor-hosted widgets**, **user content**, or **versioned legacy assets** that could restore code execution on a trusted subdomain.
746
811
- [ ] **Limit** as much as possible the **`web_accessible_resources`**, even empty if possible.
747
812
- [ ] If **`web_accessible_resources`** is not none, check for [**ClickJacking**](browext-clickjacking.md)
748
813
- [ ] If any **communication** occurs from the **extension** to the **web page**, [**check forXSS**](browext-xss-example.md) **vulnerabilities** caused in the communication.
@@ -798,9 +863,11 @@ Project Neto is a Python 3 package conceived to analyse and unravel hidden featu
0 commit comments