overview.md (49022B)
1 --- 2 title: "Browser Extension Pentesting Methodology" 3 section: "Web Pentesting" 4 sectionSlug: "pentesting-web" 5 sourcePath: "src/pentesting-web/browser-extension-pentesting-methodology/README.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/browser-extension-pentesting-methodology/README.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: true 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Browser Extension Pentesting Methodology 14 15 ## Basic Information 16 17 Browser extensions are written in JavaScript and loaded by the browser in the background. It has its [DOM](https://www.w3schools.com/js/js_htmldom.asp) but can interact with other sites' DOMs. This means that it may compromise other sites' confidentiality, integrity, and availability (CIA).<sup>[[1]](#references)</sup> 18 19 ## Main Components 20 21 An extension architecture is easiest to understand as three components.<sup>[[12]](#references)</sup> 22 23 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%2816%29%20%281%29%20%281%29.png" alt=""><figcaption><p><a href="http://webblaze.cs.berkeley.edu/papers/Extensions.pdf">http://webblaze.cs.berkeley.edu/papers/Extensions.pdf</a></p></figcaption></figure> 24 25 ### **Content Scripts** 26 27 Each content script has direct access to the DOM of a **single web page** and is thereby exposed to **potentially malicious input**. However, the content script contains no permissions other than the ability to send messages to the extension core. 28 29 ### **Extension Core** 30 31 The extension core contains most of the extension privileges/access, but the extension core can only interact with web content via [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) and content scripts. Also, the extension core does not have direct access to the host machine. 32 33 ### **Native Binary** 34 35 The extension allows a native binary that can **access the host machine with the user’s full privileges.** The native binary interacts with the extension core through the standard Netscape Plugin Application Programming Interface ([NPAPI](https://en.wikipedia.org/wiki/NPAPI)) used by Flash and other browser plug-ins. 36 37 ### Boundaries 38 39 > [!CAUTION] 40 > To obtain the user's full privileges, an attacker must convince the extension to pass malicious input from the content script to the extension's core and from the extension's core to the native binary. 41 42 Each component of the extension is separated from each other by **strong protective boundaries**. Each component runs in a **separate operating system process**. Content scripts and extension cores run in **sandbox processes** unavailable to most operating system services. 43 44 Moreover, content scripts separate from their associated web pages by **running in a separate JavaScript heap**. The content script and web page have **access to the same underlying DOM**, but the two **never exchange JavaScript pointers**, preventing the leaking of JavaScript functionality. 45 46 ## **`manifest.json`** 47 48 A Chrome extension is just a ZIP folder with a [.crx file extension](https://www.lifewire.com/crx-file-2620391). The extension's core is the **`manifest.json`** file at the root of the folder, which specifies layout, permissions, and other configuration options.<sup>[[2]](#references)</sup> 49 50 Example: 51 52 ```json 53 { 54 "manifest_version": 2, 55 "name": "My extension", 56 "version": "1.0", 57 "permissions": ["storage"], 58 "content_scripts": [ 59 { 60 "js": ["script.js"], 61 "matches": ["https://example.com/*", "https://www.example.com/*"], 62 "exclude_matches": ["*://*/*business*"] 63 } 64 ], 65 "background": { 66 "scripts": ["background.js"] 67 }, 68 "options_ui": { 69 "page": "options.html" 70 } 71 } 72 ``` 73 74 ### `content_scripts` 75 76 Content scripts are **loaded** whenever the user **navigates to a matching page**, in our case any page matching the **`https://example.com/*`** expression and not matching the **`*://*/*/business*`** regex. They execute **like the page’s own scripts** and have arbitrary access to the page’s [Document Object Model (DOM)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model). 77 78 ```json 79 "content_scripts": [ 80 { 81 "js": [ 82 "script.js" 83 ], 84 "matches": [ 85 "https://example.com/*", 86 "https://www.example.com/*" 87 ], 88 "exclude_matches": ["*://*/*business*"], 89 } 90 ], 91 ``` 92 93 Additional URL filters can be expressed with **`include_globs`** and **`exclude_globs`**.<sup>[[5]](#references)</sup> 94 95 This is an example content script which will add an explain button to the page when [the storage API](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage) to retrieve the `message` value from extension’s storage. 96 97 ```javascript 98 chrome.storage.local.get("message", (result) => { 99 let div = document.createElement("div") 100 div.innerHTML = result.message + " <button>Explain</button>" 101 div.querySelector("button").addEventListener("click", () => { 102 chrome.runtime.sendMessage("explain") 103 }) 104 document.body.appendChild(div) 105 }) 106 ``` 107 108 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%2823%29.png" alt=""><figcaption></figcaption></figure> 109 110 A message is sent to the extension pages by the content script when this button is clicked, through the utilization of the [**runtime.sendMessage() API**](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/sendMessage). This is due to the content script's limitation in direct access to APIs, with `storage` being among the few exceptions. For functionalities beyond these exceptions, messages are sent to extension pages which content scripts can communicate with. 111 112 > [!WARNING] 113 > Depending on the browser, the capabilities of the content script may vary slightly. For Chromium-based browsers, the capabilities list is available in the [Chrome Developers documentation](https://developer.chrome.com/docs/extensions/mv3/content_scripts/#capabilities), and for Firefox, the [MDN](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts#webextension_apis) serves as the primary source.<sup>[[5]](#references)</sup>\ 114 > It is also noteworthy that content scripts have the ability to communicate with background scripts, enabling them to perform actions and relay responses back. 115 116 For viewing and debugging content scripts in Chrome, the Chrome developer tools menu can be accessed from Options > More tools > Developer tools OR by pressing Ctrl + Shift + I. 117 118 Upon the developer tools being displayed, the **Source tab** is to be clicked, followed by the **Content Scripts** tab. This allows for the observation of running content scripts from various extensions and the setting of breakpoints to track the execution flow. 119 120 ### Injected content scripts 121 122 > [!TIP] 123 > **Content scripts are not mandatory**: extensions can also inject scripts dynamically with `chrome.scripting` (Manifest V3) or the legacy `tabs.executeScript` API. This provides more granular control over when injection occurs.<sup>[[5]](#references)</sup> 124 125 For the programmatic injection of a content script, the extension is required to have [host permissions](https://developer.chrome.com/docs/extensions/reference/permissions) for the page into which the scripts are to be injected. These permissions may be secured either by **requesting them** within the manifest of the extension or on a temporary basis through [**activeTab**](https://developer.chrome.com/docs/extensions/reference/manifest/activeTab). 126 127 #### Example activeTab-based extension 128 129 ```json 130 { 131 "name": "My extension", 132 ... 133 "permissions": [ 134 "activeTab", 135 "scripting" 136 ], 137 "background": { 138 "service_worker": "background.js" 139 }, 140 "action": { 141 "default_title": "Action Button" 142 } 143 } 144 ``` 145 146 - **Inject a JS file on click:** 147 148 ```javascript 149 // content-script.js 150 document.body.style.backgroundColor = "orange" 151 152 //service-worker.js - Inject the JS file 153 chrome.action.onClicked.addListener((tab) => { 154 chrome.scripting.executeScript({ 155 target: { tabId: tab.id }, 156 files: ["content-script.js"], 157 }) 158 }) 159 ``` 160 161 - **Inject a function** on click: 162 163 ```javascript 164 //service-worker.js - Inject a function 165 function injectedFunction() { 166 document.body.style.backgroundColor = "orange" 167 } 168 169 chrome.action.onClicked.addListener((tab) => { 170 chrome.scripting.executeScript({ 171 target: { tabId: tab.id }, 172 func: injectedFunction, 173 }) 174 }) 175 ``` 176 177 #### Example with scripting permissions 178 179 ```javascript 180 // service-workser.js 181 chrome.scripting.registerContentScripts([ 182 { 183 id: "test", 184 matches: ["https://*.example.com/*"], 185 excludeMatches: ["*://*/*business*"], 186 js: ["contentScript.js"], 187 }, 188 ]) 189 190 // Another example 191 chrome.tabs.executeScript(tabId, { file: "content_script.js" }) 192 ``` 193 194 ### Content Scripts `run_at` 195 196 The `run_at` field controls **when JavaScript files are injected into the web page**. The preferred and default value is `"document_idle"`. 197 198 The possible values are: 199 200 - **`document_idle`**: Whenever possible 201 - **`document_start`**: After any files from `css`, but before any other DOM is constructed or any other script is run. 202 - **`document_end`**: Immediately after the DOM is complete, but before subresources like images and frames have loaded. 203 204 #### Via `manifest.json` 205 206 ```json 207 { 208 "name": "My extension", 209 ... 210 "content_scripts": [ 211 { 212 "matches": ["https://*.example.com/*"], 213 "run_at": "document_idle", 214 "js": ["contentScript.js"] 215 } 216 ], 217 ... 218 } 219 220 ``` 221 222 Via **`service-worker.js`** 223 224 ```javascript 225 chrome.scripting.registerContentScripts([ 226 { 227 id: "test", 228 matches: ["https://*.example.com/*"], 229 runAt: "document_idle", 230 js: ["contentScript.js"], 231 }, 232 ]) 233 ``` 234 235 ### `background` 236 237 Messages sent by content scripts are received by the **background page**, which serves a central role in coordinating the extension's components. Notably, the background page persists across the extension's lifetime, operating discreetly without direct user interaction. It possesses its own Document Object Model (DOM), enabling complex interactions and state management.<sup>[[7]](#references)</sup> 238 239 **Key Points**: 240 241 - **Background Page Role:** Acts as the nerve center for the extension, ensuring communication and coordination among various parts of the extension. 242 - **Persistence:** It's an ever-present entity, invisible to the user but integral to the extension's functionality. 243 - **Automatic Generation:** If not explicitly defined, the browser will automatically create a background page. This auto-generated page will include all the background scripts specified in the extension's manifest, ensuring the seamless operation of the extension's background tasks. 244 245 > [!TIP] 246 > The convenience provided by the browser in automatically generating a background page (when not explicitly declared) ensures that all necessary background scripts are integrated and operational, streamlining the extension's setup process. 247 248 Example background script: 249 250 ```javascript 251 chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { 252 if (request == "explain") { 253 chrome.tabs.create({ url: "https://example.net/explanation" }) 254 } 255 }) 256 ``` 257 258 It uses [runtime.onMessage API](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage) to listen to messages. When an `"explain"` message is received, it uses [tabs API](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs) to open a page in a new tab. 259 260 To debug the background script you could go to the **extension details and inspect the service worker,** this will open the developer tools with the background script: 261 262 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/browser-extension-pentesting-methodology/broken-reference" alt=""><figcaption></figcaption></figure> 263 264 ### Options pages and other 265 266 Browser extensions can contain various kinds of pages: 267 268 - **Action pages** are displayed in a **drop-down when the extension ico**n is clicked. 269 - Pages that the extension will **load in a new tab**. 270 - **Option Pages**: This page displays on top of the extension when clicked. In the previous manifest In my case I was able to access this page in `chrome://extensions/?options=fadlhnelkbeojnebcbkacjilhnbjfjca` or clicking: 271 272 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%2824%29.png" alt="" width="375"><figcaption></figcaption></figure> 273 274 Note that these pages aren't persistent like background pages as they load dynamically content on necessity. Despite this, they share certain capabilities with the background page: 275 276 - **Communication with Content Scripts:** Similar to the background page, these pages can receive messages from content scripts, facilitating interaction within the extension. 277 - **Access to Extension-Specific APIs:** These pages enjoy comprehensive access to extension-specific APIs, subject to the permissions defined for the extension. 278 279 ### `permissions` & `host_permissions` 280 281 **`permissions`** and **`host_permissions`** are entries from the `manifest.json` that will indicate **which permissions** the browser extensions has (storage, location...) and in **which web pages**.<sup>[[3]](#references)</sup> 282 283 As browser extensions can be so **privileged**, a malicious one or one being compromised could allow the attacker **different means to steal sensitive information and spy on the user**. 284 285 Check how these settings work and how they could get abused in: 286 287 288 [Browext Permissions And Host Permissions](/hacktricks/pentesting-web/browser-extension-pentesting-methodology/browext-permissions-and-host-permissions) 289 290 ### `content_security_policy` 291 292 A **content security policy** can be declared also inside the `manifest.json`. If there is one defined, it could be **vulnerable**. 293 294 The default setting for browser extension pages is rather restrictive: 295 296 ```bash 297 script-src 'self'; object-src 'self'; 298 ``` 299 300 For more info about CSP and potential bypasses check: 301 302 303 [Content Security Policy Csp Bypass](/hacktricks/pentesting-web/content-security-policy-csp-bypass/overview) 304 305 ### `web_accessible_resources` 306 307 in order for a webpage to access a page of a Browser Extension, a `.html` page for example, this page needs to be mentioned in the **`web_accessible_resources`** field of the `manifest.json`.<sup>[[4]](#references)</sup>\ 308 For example: 309 310 ```javascript 311 { 312 ... 313 "web_accessible_resources": [ 314 { 315 "resources": [ "images/*.png" ], 316 "matches": [ "https://example.com/*" ] 317 }, 318 { 319 "resources": [ "fonts/*.woff" ], 320 "matches": [ "https://example.com/*" ] 321 } 322 ], 323 ... 324 } 325 ``` 326 327 These pages are accesible in URL like: 328 329 ```text 330 chrome-extension://<extension-id>/message.html 331 ``` 332 333 In public extensions the **extension-id is accesible**: 334 335 <figure><img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%281194%29.png" alt="" width="375"><figcaption></figcaption></figure> 336 337 Although, if the `manifest.json` parameter **`use_dynamic_url`** is used, this **id can be dynamic**. 338 339 > [!TIP] 340 > Note that even if a page is mentioned here, it might be **protected against ClickJacking** thanks to the **Content Security Policy**. So you also need to check it (frame-ancestors section) before confirming a ClickJacking attack is possible. 341 342 Being allowed to access these pages make these pages **potentially vulnerable ClickJacking**: 343 344 345 [Browext Clickjacking](/hacktricks/pentesting-web/browser-extension-pentesting-methodology/browext-clickjacking) 346 347 > [!TIP] 348 > Allowing these pages to be loaded only by the extension and not by random URLs could prevent ClickJacking attacks. 349 350 > [!CAUTION] 351 > Note that the pages from **`web_accessible_resources`** and other pages of the extension are also capable of **contacting background scripts**. So if one of these pages is vulnerable to **XSS** it could open a bigger vulnerability. 352 > 353 > Moreover, note that you can only open pages indicated in **`web_accessible_resources`** inside iframes, but from a new tab it's possible to access any page in the extension knowing the extension ID. Therefore, if an XSS is found abusing same parameters, it could be abused even if the page isn't configured in **`web_accessible_resources`**. 354 355 ### `externally_connectable` 356 357 A per the [**docs**](https://developer.chrome.com/docs/extensions/reference/manifest/externally-connectable)<sup>[[6]](#references)</sup>, The `"externally_connectable"` manifest property declares **which extensions and web pages can connect** to your extension via [runtime.connect](https://developer.chrome.com/docs/extensions/reference/runtime#method-connect) and [runtime.sendMessage](https://developer.chrome.com/docs/extensions/reference/runtime#method-sendMessage). 358 359 - If the **`externally_connectable`** key is **not** declared in your extension's manifest or it's declared as **`"ids": ["*"]`**, **all extensions can connect, but no web pages can connect**. 360 - If **specific IDs are specified**, like in `"ids": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]`, **only those applications** can connect. 361 - If **matches** are specified, those web apps will be able to connect: 362 363 ```json 364 "matches": [ 365 "https://*.google.com/*", 366 "*://*.chromium.org/*", 367 ``` 368 369 - If it's specified as empty: **`"externally_connectable": {}`**, no app or web will be able to connect. 370 371 The **less extensions and URLs** indicated here, the **smaller the attack surface** will be. 372 373 > [!CAUTION] 374 > If a web page **vulnerable to XSS or takeover** is indicated in **`externally_connectable`**, an attacker will be able to **send messages directly to the background script**, completely bypassing the Content Script and its CSP. 375 > 376 > Therefore, this is a **very powerful bypass**. 377 > 378 > Moreover, if the client installs a rogue extension, it could inject **XSS data into an allowed web page** or abuse **`webRequest`** or **`declarativeNetRequest`** to alter a target page's request for a **JavaScript file**, even when it cannot communicate with the vulnerable extension directly. The target page's CSP may constrain this chain.<sup>[[14]](#references)</sup> 379 380 #### Wildcard-trusted web origins to privileged action injection 381 382 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.<sup>[[11]](#references)</sup> 383 384 Typical exploitation path: 385 386 1. Find the extension ID and enumerate `externally_connectable.matches`. 387 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). 388 3. Get JavaScript execution in **any** trusted origin. 389 4. Send the request directly to the extension: 390 391 ```javascript 392 chrome.runtime.sendMessage("<extension-id>", { 393 type: "privileged_action", 394 payload: { attacker: "controlled" }, 395 }) 396 ``` 397 398 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. 399 400 > [!CAUTION] 401 > 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. 402 403 #### Auditing `chrome.runtime.sendMessage` / `onMessageExternal` trust 404 405 When reviewing a browser extension that accepts messages from web pages: 406 407 - Search for `chrome.runtime.onMessageExternal.addListener`, `chrome.runtime.onConnectExternal.addListener`, and sensitive handlers reachable from those listeners. 408 - Verify the extension performs **exact origin equality** checks on the sender instead of suffix, regex, or wildcard matching. 409 - Verify the handler authorizes **message type + origin** together. A safe origin for telemetry or onboarding is not automatically safe for privileged actions. 410 - Confirm the extension does not trust a subdomain just because it shares the parent eTLD+1. 411 - Check whether any allowed origin hosts **third-party JavaScript**, **user-generated content**, or **legacy static assets**. 412 413 Bad patterns: 414 415 ```javascript 416 if (sender.origin.endsWith(".example.com")) { /* trust */ } 417 if (/^https:\/\/.*\.example\.com$/.test(sender.origin)) { /* trust */ } 418 ``` 419 420 Prefer strict matching to the minimal set of origins: 421 422 ```javascript 423 if (sender.origin !== "https://app.example.com") return 424 ``` 425 426 #### Rollback hunting on trusted static assets 427 428 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. 429 430 Common patterns: 431 432 - `/assets/widget/1.26.0/index.html` 433 - `/static/app-2024.12.1/` 434 - `/cdn/component/v1234/` 435 436 Practical checks: 437 438 - Start from a version observed in network traffic or page source. 439 - Decrement semantic versions / build numbers and request older paths. 440 - Look for `200`, `301`, or cache hits on old builds. 441 - Review older JS bundles for DOM XSS, unsafe message handlers, or gadget endpoints that can re-establish JavaScript execution on the trusted origin. 442 443 ## Communication summary 444 445 ### Extension <--> WebApp 446 447 To communicate between the content script and the web page post messages are usually used. Therefore, in the web application you will usually find calls to the function **`window.postMessage`** and in the content script listeners like **`window.addEventListener`**. Note however, that the extension could also **communicate with the web application sending a Post Message** (and therefore the web should expect it) or just make the web load a new script. 448 449 ### Inside the extension 450 451 Usually the function **`chrome.runtime.sendMessage`** is used to send a message inside the extension (usually handled by the `background` script) and in order to receive and handle it a listener is declared calling **`chrome.runtime.onMessage.addListener`**. 452 453 It's also possible to use **`chrome.runtime.connect()`** to have a persistent connection instead of sending single messages, it's possible to use it to **send** and **receive** **messages** like in the following example: 454 455 <details> 456 457 <summary><code>chrome.runtime.connect()</code> example</summary> 458 459 ```javascript 460 var port = chrome.runtime.connect() 461 462 // Listen for messages from the web page 463 window.addEventListener( 464 "message", 465 (event) => { 466 // Only accept messages from the same window 467 if (event.source !== window) { 468 return 469 } 470 471 // Check if the message type is "FROM_PAGE" 472 if (event.data.type && event.data.type === "FROM_PAGE") { 473 console.log("Content script received: " + event.data.text) 474 // Forward the message to the background script 475 port.postMessage({ type: "FROM_PAGE", text: event.data.text }) 476 } 477 }, 478 false 479 ) 480 481 // Listen for messages from the background script 482 port.onMessage.addListener(function (msg) { 483 console.log("Content script received message from background script:", msg) 484 // Handle the response message from the background script 485 }) 486 ``` 487 488 </details> 489 490 It's also possible to send messages from a background script to a content script located in a specific tab calling **`chrome.tabs.sendMessage`** where you will need to indicated the **ID of the tab** to send the message to. 491 492 ### From allowed `externally_connectable` to the extension 493 494 **Web apps and external browser extensions allowed** in the `externally_connectable` configuration can send requests using : 495 496 ```javascript 497 chrome.runtime.sendMessage(extensionId, ... 498 ``` 499 500 Where it's needed to mention the **extension ID**. 501 502 ### Native Messaging 503 504 It's possible for the background scripts to communicate with binaries inside the system, which might be **prone to critical vulnerabilities such as RCEs** if this communication is not properly secured. [More on this later](#native-messaging). 505 506 ```javascript 507 chrome.runtime.sendNativeMessage( 508 "com.my_company.my_application", 509 { text: "Hello" }, 510 function (response) { 511 console.log("Received " + response) 512 } 513 ) 514 ``` 515 516 ## Web **↔︎** Content Script Communication 517 518 The environments where **content scripts** operate and where the host pages exist are **separated** from one another, ensuring **isolation**. Despite this isolation, both have the ability to interact with the page's **Document Object Model (DOM)**, a shared resource. For the host page to engage in communication with the **content script**, or indirectly with the extension through the content script, it is required to utilize the **DOM** that is accessible by both parties as the communication channel. 519 520 ### Post Messages 521 522 ```javascript 523 // This is like "chrome.runtime.sendMessage" but to maintain the connection 524 var port = chrome.runtime.connect() 525 526 window.addEventListener( 527 "message", 528 (event) => { 529 // We only accept messages from ourselves 530 if (event.source !== window) { 531 return 532 } 533 534 if (event.data.type && event.data.type === "FROM_PAGE") { 535 console.log("Content script received: " + event.data.text) 536 // Forward the message to the background script 537 port.postMessage(event.data.text) 538 } 539 }, 540 false 541 ) 542 ``` 543 544 ```javascript 545 document.getElementById("theButton").addEventListener( 546 "click", 547 () => { 548 window.postMessage( 549 { type: "FROM_PAGE", text: "Hello from the webpage!" }, 550 "*" 551 ) 552 }, 553 false 554 ) 555 ``` 556 557 A secure Post Message communication should check the authenticity of the received message, this can be done checking:<sup>[[1]](#references)</sup> 558 559 - **`event.isTrusted`**: This is True only if the event was triggered by a users action 560 - The content script might expecting a message only if the user performs some action 561 - **origin domain**: might expecting a message only allowlist of domains. 562 - If a regex is used, be very careful 563 - **Source**: `received_message.source !== window` can be used to check if the message was **from the same window** where the Content Script is listening. 564 565 The previous checks, even if performed, could be vulnerable, so check in the following page **potential Post Message bypasses**: 566 567 568 [Postmessage Vulnerabilities](/hacktricks/pentesting-web/postmessage-vulnerabilities/overview) 569 570 ### Iframe 571 572 Another possible way of communication might be through **Iframe URLs**, you can find an example in: 573 574 575 [Browext Xss Example](/hacktricks/pentesting-web/browser-extension-pentesting-methodology/browext-xss-example) 576 577 ### DOM 578 579 This isn't "exactly" a communication way, but the **web and the content script will have access to the web DOM**. So, if the **content script** is reading some information from it, **trusting the web DOM**, the web could **modify this dat**a (because the web shouldn't be trusted, or because the web is vulnerable to XSS) and **compromise the Content Script**. 580 581 You can also find an example of a **DOM based XSS to compromise a browser extension** in: 582 583 584 [Browext Xss Example](/hacktricks/pentesting-web/browser-extension-pentesting-methodology/browext-xss-example) 585 586 ## Content Script **↔︎** Background Script Communication 587 588 A Content Script can use the functions [**runtime.sendMessage()**](https://developer.chrome.com/docs/extensions/reference/runtime#method-sendMessage) **or** [**tabs.sendMessage()**](https://developer.chrome.com/docs/extensions/reference/tabs#method-sendMessage) to send a **one-time JSON-serializable** message. 589 590 To handle the **response**, use the returned **Promise**. Although, for backward compatibility, you can still pass a **callback** as the last argument. 591 592 Sending a request from a **content script** looks like this: 593 594 ```javascript 595 ;(async () => { 596 const response = await chrome.runtime.sendMessage({ greeting: "hello" }) 597 // do something with response here, not outside the function 598 console.log(response) 599 })() 600 ``` 601 602 Sending a request from the **extension** (usually a **background script**). Example of how to send message to the content script in the selected tab: 603 604 ```javascript 605 // From https://stackoverflow.com/questions/36153999/how-to-send-a-message-between-chrome-extension-popup-and-content-script 606 ;(async () => { 607 const [tab] = await chrome.tabs.query({ 608 active: true, 609 lastFocusedWindow: true, 610 }) 611 const response = await chrome.tabs.sendMessage(tab.id, { greeting: "hello" }) 612 // do something with response here, not outside the function 613 console.log(response) 614 })() 615 ``` 616 617 On the **receiving end**, you need to set up an [**runtime.onMessage**](https://developer.chrome.com/docs/extensions/reference/runtime#event-onMessage) **event listener** to handle the message. This looks the same from a content script or extension page. 618 619 ```javascript 620 // From https://stackoverflow.com/questions/70406787/javascript-send-message-from-content-js-to-background-js 621 chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { 622 console.log( 623 sender.tab 624 ? "from a content script:" + sender.tab.url 625 : "from the extension" 626 ) 627 if (request.greeting === "hello") sendResponse({ farewell: "goodbye" }) 628 }) 629 ``` 630 631 In the example highlighted, **`sendResponse()`** was executed in a synchronous fashion. To modify the `onMessage` event handler for asynchronous execution of `sendResponse()`, it's imperative to incorporate `return true;`. 632 633 An important consideration is that in scenarios where multiple pages are set to receive `onMessage` events, **the first page to execute `sendResponse()`** for a specific event will be the only one able to deliver the response effectively. Any subsequent responses to the same event will not be taken into account. 634 635 When crafting new extensions, the preference should be towards promises as opposed to callbacks. Concerning the use of callbacks, the `sendResponse()` function is considered valid only if it's executed directly within the synchronous context, or if the event handler indicates an asynchronous operation by returning `true`. Should none of the handlers return `true` or if the `sendResponse()` function is removed from memory (garbage-collected), the callback associated with the `sendMessage()` function will be triggered by default. 636 637 ## Native Messaging 638 639 Browser extensions also allow to communicate with **binaries in the system via stdin**. The application must install a json indicating so in a json like: 640 641 ```json 642 { 643 "name": "com.my_company.my_application", 644 "description": "My Application", 645 "path": "C:\\Program Files\\My Application\\chrome_native_messaging_host.exe", 646 "type": "stdio", 647 "allowed_origins": ["chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"] 648 } 649 ``` 650 651 Where the `name` is the string passed to [`runtime.connectNative()`](https://developer.chrome.com/docs/extensions/reference/api/runtime#method-connectNative) or [`runtime.sendNativeMessage()`](https://developer.chrome.com/docs/extensions/reference/api/runtime#method-sendNativeMessage) to communicate with the application from the background scripts of the browser extension. The `path` is the path to the binary, there is only 1 valid `type` which is stdio (use stdin and stdout) and the `allowed_origins` indicate the extensions that can access it (and can't have wildcard). 652 653 Chrome/Chromium will search for this json in some windows registry and some paths in macOS and Linux (more info in the [**docs**](https://developer.chrome.com/docs/extensions/develop/concepts/native-messaging)).<sup>[[17]](#references)</sup> 654 655 > [!TIP] 656 > The browser extension also needs the `nativeMessaing` permission declared in order to be able to use this communication. 657 658 This is how it looks like some background script code sending messages to a native application: 659 660 ```javascript 661 chrome.runtime.sendNativeMessage( 662 "com.my_company.my_application", 663 { text: "Hello" }, 664 function (response) { 665 console.log("Received " + response) 666 } 667 ) 668 ``` 669 670 In [**this blog post**](https://spaceraccoon.dev/universal-code-execution-browser-extensions/)<sup>[[13]](#references)</sup>, a vulnerable pattern abusing native messages is proposed: 671 672 1. Browser extension has a wildcard pattern for content script. 673 2. Content script passes `postMessage` messages to the background script using `sendMessage`. 674 3. Background script passes the message to native application using `sendNativeMessage`. 675 4. Native application handles the message dangerously, leading to code execution. 676 677 And inside of it an example of **going from any page to RCE abusing a browser extension is explained**. 678 679 ## Sensitive Information in Memory/Code/Clipboard 680 681 If a browser extension stores **sensitive information in its memory**, a local process dump—especially on Windows—may expose it.<sup>[[1]](#references)</sup> 682 683 Therefore, the memory of the Browser Extension **shouldn't be considered secure** and **sensitive information** such as credentials or mnemonic phrases **shouldn't be stored**. 684 685 Of course, do **not put sensitive information in the code**, as it will be **public**. 686 687 To dump memory from the browser you could **dump the process memory** or to go to the **settings** of the browser extension click on **`Inspect pop-up`** -> In the **`Memory`** section -> **`Take a snaphost`** and **`CTRL+F`** to search inside the snapshot for sensitive info. 688 689 Moreover, highly sensitive information like mnemonic keys or passwords **shouldn't be allowed to be copied in the clipboard** (or at least remove it from the clipboard in a few seconds) because then processes monitoring the clipboard will be able to get them.<sup>[[16]](#references)</sup> 690 691 ## Loading an Extension in the Browser 692 693 1. **Download** the Browser Extension & unzipped 694 2. Go to **`chrome://extensions/`** and **enable** the `Developer Mode` 695 3. Click the **`Load unpacked`** button 696 697 In **Firefox** you go to **`about:debugging#/runtime/this-firefox`** and click **`Load Temporary Add-on`** button. 698 699 ## Getting the source code from the store 700 701 The source code of a Chrome extension can be obtained through various methods. Below are detailed explanations and instructions for each option.<sup>[[9]](#references)</sup> 702 703 ### Download Extension as ZIP via Command Line 704 705 The source code of a Chrome extension can be downloaded as a ZIP file using the command line. This involves using `curl` to fetch the ZIP file from a specific URL and then extracting the contents of the ZIP file to a directory. Here are the steps: 706 707 1. Replace `"extension_id"` with the actual ID of the extension. 708 2. Execute the following commands: 709 710 ```bash 711 extension_id=your_extension_id # Replace with the actual extension ID 712 curl -L -o "$extension_id.zip" "https://clients2.google.com/service/update2/crx?response=redirect&os=mac&arch=x86-64&nacl_arch=x86-64&prod=chromecrx&prodchannel=stable&prodversion=44.0.2403.130&x=id%3D$extension_id%26uc" 713 unzip -d "$extension_id-source" "$extension_id.zip" 714 ``` 715 716 ### Use the CRX Viewer website 717 718 [https://robwu.nl/crxviewer/](https://robwu.nl/crxviewer/) 719 720 ### Use the CRX Viewer extension 721 722 Another convenient method is using the Chrome Extension Source Viewer, which is an open-source project. It can be installed from the [Chrome Web Store](https://chrome.google.com/webstore/detail/chrome-extension-source-v/jifpbeccnghkjeaalbbjmodiffmgedin?hl=en). The source code of the viewer is available in its [GitHub repository](https://github.com/Rob--W/crxviewer). 723 724 ### View source of locally installed extension 725 726 Chrome extensions installed locally can also be inspected. Here's how: 727 728 1. Access your Chrome local profile directory by visiting `chrome://version/` and locating the "Profile Path" field. 729 2. Navigate to the `Extensions/` subfolder within the profile directory. 730 3. This folder contains all installed extensions, typically with their source code in a readable format. 731 732 To identify extensions, you can map their IDs to names: 733 734 - Enable Developer Mode on the `about:extensions` page to see the IDs of each extension. 735 - Within each extension's folder, the `manifest.json` file contains a readable `name` field, helping you to identify the extension. 736 737 ### Use a File Archiver or Unpacker 738 739 Go to the Chrome Web Store and download the extension. The file will have a `.crx` extension. Change the file extension from `.crx` to `.zip`. Use any file archiver (like WinRAR, 7-Zip, etc.) to extract the contents of the ZIP file. 740 741 ### Use Developer Mode in Chrome 742 743 Open Chrome and go to `chrome://extensions/`. Enable "Developer mode" at the top right. Click on "Load unpacked extension...". Navigate to the directory of your extension. This doesn't download the source code, but it's useful for viewing and modifying the code of an already downloaded or developed extension. 744 745 ## Chrome extension manifest dataset 746 747 In order to try to spot vulnerable browser extensions you could use the[https://github.com/palant/chrome-extension-manifests-dataset](https://github.com/palant/chrome-extension-manifests-dataset) and check their manifest files for potentially vulnerable signs. For example to check for extensions with more than 25000 users, `content_scripts` and the permission `nativeMessaing`:<sup>[[13]](#references)</sup> 748 749 ```bash 750 # Query example from https://spaceraccoon.dev/universal-code-execution-browser-extensions/ 751 node query.js -f "metadata.user_count > 250000" "manifest.content_scripts?.length > 0 && manifest.permissions?.includes('nativeMessaging')" 752 ``` 753 754 ## Post-exploitation: Forced extension load & persistence (Windows) 755 756 Stealthy technique to backdoor Chromium by directly editing per-user Preferences and forging valid HMACs, causing the browser to accept and activate an arbitrary unpacked extension without prompts or flags. 757 758 [Forced Extension Load Preferences Mac Forgery Windows](/hacktricks/pentesting-web/browser-extension-pentesting-methodology/forced-extension-load-preferences-mac-forgery-windows) 759 760 ## Detecting Malicious Extension Updates (Static Version Diffing) 761 762 Supply-chain compromises often arrive as **malicious updates** to previously benign extensions. A practical, low-noise approach is to **compare a new extension package against the last known-good version** using static analysis (for example, [Assemblyline](https://github.com/CybercentreCanada/assemblyline)). The goal is to alert on **high-signal deltas** rather than on any change.<sup>[[10]](#references)</sup> 763 764 ### Workflow 765 766 - **Submit both versions** (old + new) to the same static-analysis profile. 767 - **Flag new or updated background/service worker scripts** (persistence + privileged logic). 768 - **Flag new or updated content scripts** (DOM access and data collection). 769 - **Flag new permissions/host_permissions** added in `manifest.json`. 770 - **Flag new domains** extracted from code (potential C2/exfil endpoints). 771 - **Flag new static-analysis detections** (e.g., base64 decode, cookie harvesting, network-request builders, obfuscation patterns). 772 - **Flag statistical anomalies** such as sharp entropy jumps or outlier z-scores in changed scripts. 773 774 ### Detecting script changes accurately 775 776 - **New script added** → detect via `manifest.json` diff. 777 - **Existing script modified** (manifest unchanged) → compare **per-file hashes** from the extracted file tree (e.g., Assemblyline `Extract` output). This catches stealthy updates to existing workers or content scripts. 778 779 ### Pre-disclosure detections 780 781 To avoid “easy mode” detections based on already-known IOCs, **disable threat-intel-fed services** and rely on intrinsic signals (domains, heuristic signatures, script deltas, entropy anomalies). This increases chances of catching malicious updates **before public reporting**. 782 783 ### Example high-confidence alert logic 784 785 - **Low-noise combo:** new domains + new static-analysis detections + updated background/service worker + updated or added content scripts. 786 - **Broader catch:** new domain + new or updated background/service worker (higher recall, higher noise). 787 788 Key Assemblyline services for this workflow: 789 790 - **Extract**: unpacks the extension and yields per-file hashes. 791 - **Characterize**: computes file characteristics (e.g., entropy). 792 - **JsJAWS / FrankenStrings / URLCreator**: surface JS heuristics, strings, and domains to diff between versions. 793 794 ## Security Audit Checklist 795 796 Even though Browser Extensions have a **limited attack surface**, some of them might contain **vulnerabilities** or **potential hardening improvements**. The following ones are the most common ones:<sup>[[8]](#references)</sup> 797 798 - [ ] **Limit** as much as possible requested **`permissions`** 799 - [ ] **Limit** as much as possible **`host_permissions`** 800 - [ ] Use a **strong** **`content_security_policy`** 801 - [ ] **Limit** as much as possible the **`externally_connectable`**, if none is needed and possible, do not leave it by default, specify **`{}`** 802 - [ ] 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. 803 - [ ] Reject wildcard or suffix trust such as `*.example.com` for **privileged** external message handlers. 804 - [ ] Review whether any allowed origin serves **vendor-hosted widgets**, **user content**, or **versioned legacy assets** that could restore code execution on a trusted subdomain. 805 - [ ] **Limit** as much as possible the **`web_accessible_resources`**, even empty if possible. 806 - [ ] If **`web_accessible_resources`** is not none, check for [**ClickJacking**](/hacktricks/pentesting-web/browser-extension-pentesting-methodology/browext-clickjacking) 807 - [ ] If any **communication** occurs from the **extension** to the **web page**, [**check for XSS**](/hacktricks/pentesting-web/browser-extension-pentesting-methodology/browext-xss-example) **vulnerabilities** caused in the communication. 808 - [ ] If Post Messages are used, check for [**Post Message vulnerabilities**](../postmessage-vulnerabilities/index.html)**.** 809 - [ ] If the **Content Script access DOM details**, check that they **aren't introducing a XSS** if they get **modified** by the web 810 - [ ] Make a special emphasis if this communication is also involved in the **Content Script -> Background script communication** 811 - [ ] If the background script is communicating via **native messaging** check the communication is secure and sanitized 812 - [ ] **Sensitive information shouldn't be stored** inside the Browser Extension **code** 813 - [ ] **Sensitive information shouldn't be stored** inside the Browser Extension **memory** 814 - [ ] **Sensitive information shouldn't be stored** inside the **file system unprotected** 815 816 ## Browser Extension Risks 817 818 - The app [https://crxaminer.tech/](https://crxaminer.tech/) analyzes some data like the permissions browser extension requests to give a risk level of using the browser extension. 819 820 ## Tools 821 822 ### [**Tarnish**](https://thehackerblog.com/tarnish/) 823 824 - Pulls any Chrome extension from a provided Chrome webstore link.<sup>[[1]](#references)</sup> 825 - [**manifest.json**](https://developer.chrome.com/extensions/manifest) **viewer**: simply displays a JSON-prettified version of the extension’s manifest. 826 - **Fingerprint Analysis**: Detection of [web_accessible_resources](https://developer.chrome.com/extensions/manifest/web_accessible_resources) and automatic generation of Chrome extension fingerprinting JavaScript. 827 - **Potential Clickjacking Analysis**: Detection of extension HTML pages with the [web_accessible_resources](https://developer.chrome.com/extensions/manifest/web_accessible_resources) directive set. These are potentially vulnerable to clickjacking depending on the purpose of the pages. 828 - **Permission Warning(s) viewer**: which shows a list of all the Chrome permission prompt warnings which will be displayed upon a user attempting to install the extension. 829 - **Dangerous Function(s)**: shows the location of dangerous functions which could potentially be exploited by an attacker (e.g. functions such as innerHTML, chrome.tabs.executeScript). 830 - **Entry Point(s)**: shows where the extension takes in user/external input. This is useful for understanding an extension’s surface area and looking for potential points to send maliciously-crafted data to the extension. 831 - Both the Dangerous Function(s) and Entry Point(s) scanners have the following for their generated alerts: 832 - Relevant code snippet and line that caused the alert. 833 - Description of the issue. 834 - A “View File” button to view the full source file containing the code. 835 - The path of the alerted file. 836 - The full Chrome extension URI of the alerted file. 837 - The type of file it is, such as a Background Page script, Content Script, Browser Action, etc. 838 - If the vulnerable line is in a JavaScript file, the paths of all of the pages where it is included as well as these page’s type, and [web_accessible_resource](https://developer.chrome.com/extensions/manifest/web_accessible_resources) status. 839 - **Content Security Policy (CSP) analyzer and bypass checker**: This will point out weaknesses in your extension’s CSP and will also illuminate any potential ways to bypass your CSP due to whitelisted CDNs, etc. 840 - **Known Vulnerable Libraries**: This uses [Retire.js](https://retirejs.github.io/retire.js/) to check for any usage of known-vulnerable JavaScript libraries. 841 - Download extension and formatted versions. 842 - Download the original extension. 843 - Download a beautified version of the extension (auto prettified HTML and JavaScript). 844 - Automatic caching of scan results, running an extension scan will take a good amount of time the first time you run it. However the second time, assuming the extension hasn’t been updated, will be almost instant due to the results being cached. 845 - Linkable Report URLs, easily link someone else to an extension report generated by tarnish. 846 847 ### [Neto](https://github.com/elevenpaths/neto) 848 849 Project Neto is a Python 3 package for analyzing browser plugins and extensions for browsers such as Firefox and Chrome. It unpacks extension packages and extracts relevant features from resources such as `manifest.json`, localization folders, JavaScript, and HTML files. 850 851 **Thanks to** [**@naivenom**](https://twitter.com/naivenom) **for the help with this methodology.**<sup>[[15]](#references)</sup> 852 853 ## References 854 855 - [1] [Introduction to Chrome Browser Extension Security Testing](https://www.cobalt.io/blog/introduction-to-chrome-browser-extension-security-testing) 856 - [2] [Anatomy of a Basic Extension](https://palant.info/2022/08/10/anatomy-of-a-basic-extension/) 857 - [3] [Attack Surface of Extension Pages](https://palant.info/2022/08/24/attack-surface-of-extension-pages/) 858 - [4] [When Extension Pages Are Web-Accessible](https://palant.info/2022/08/31/when-extension-pages-are-web-accessible/) 859 - [5] [Content scripts - Chrome for Developers](https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts) 860 - [6] [externally_connectable - Chrome for Developers](https://developer.chrome.com/docs/extensions/reference/manifest/externally-connectable) 861 - [7] [Background Pages (Manifest V2) - Chrome for Developers](https://developer.chrome.com/docs/extensions/mv2/background-pages) 862 - [8] [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/) 863 - [9] [How to View Source of a Chrome Extension (gist)](https://gist.github.com/LongJohnCoder/9ddf5735df3a4f2e9559665fb864eac0) 864 - [10] [Moving up the Assemblyline: Exposing Malicious Code in Browser Extensions](https://redcanary.com/blog/threat-detection/assemblyline-browser-extensions/) 865 - [11] [ShadowPrompt: How Any Website Could Have Hijacked Anthropic's Claude Chrome Extension](https://www.koi.ai/blog/shadowprompt-how-any-website-could-have-hijacked-anthropic-claude-chrome-extension) 866 - [12] [An Evaluation of the Google Chrome Extension Security Architecture](http://webblaze.cs.berkeley.edu/papers/Extensions.pdf) 867 - [13] [Universal Code Execution in Browser Extensions](https://spaceraccoon.dev/universal-code-execution-browser-extensions/) 868 - [14] [Opera Browser Zero-Day RCE Vulnerability on Cross-Platforms](https://www.darkrelay.com/post/opera-zero-day-rce-vulnerability) 869 - [15] Thanks to [@naivenom](https://twitter.com/naivenom) for the help with this methodology 870 - [16] [Passbolt PBL-02 security report](https://help.passbolt.com/assets/files/PBL-02-report.pdf) 871 - [17] [developer.chrome.com - Concepts - Native Messaging](https://developer.chrome.com/docs/extensions/develop/concepts/native-messaging)