mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-24 03:53:29 +00:00
793 lines
42 KiB
Markdown
793 lines
42 KiB
Markdown
# Browser Extension Pentesting Methodology
|
||
|
||
{% hint style="success" %}
|
||
Learn & practice AWS Hacking:<img src="../../.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../../.gitbook/assets/arte.png" alt="" data-size="line">\
|
||
Learn & practice GCP Hacking: <img src="../../.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="../../.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
||
|
||
<details>
|
||
|
||
<summary>Support HackTricks</summary>
|
||
|
||
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
|
||
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks_live)**.**
|
||
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
|
||
|
||
</details>
|
||
{% endhint %}
|
||
|
||
## Basic Information
|
||
|
||
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).
|
||
|
||
## Main Components
|
||
|
||
Extension layouts look best when visualised and consists of three components. Let’s look at each component in depth.
|
||
|
||
<figure><img src="../../.gitbook/assets/image (16) (1) (1).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>
|
||
|
||
### **Content Scripts**
|
||
|
||
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.
|
||
|
||
### **Extension Core**
|
||
|
||
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.
|
||
|
||
### **Native Binary**
|
||
|
||
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.
|
||
|
||
### Boundaries
|
||
|
||
{% hint style="danger" %}
|
||
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.
|
||
{% endhint %}
|
||
|
||
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.
|
||
|
||
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.
|
||
|
||
## **`manifest.json`**
|
||
|
||
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.
|
||
|
||
Example:
|
||
|
||
```json
|
||
{
|
||
"manifest_version": 2,
|
||
"name": "My extension",
|
||
"version": "1.0",
|
||
"permissions": [
|
||
"storage"
|
||
],
|
||
"content_scripts": [
|
||
{
|
||
"js": [
|
||
"script.js"
|
||
],
|
||
"matches": [
|
||
"https://example.com/*",
|
||
"https://www.example.com/*"
|
||
],
|
||
"exclude_matches": ["*://*/*business*"],
|
||
}
|
||
],
|
||
"background": {
|
||
"scripts": [
|
||
"background.js"
|
||
]
|
||
},
|
||
"options_ui": {
|
||
"page": "options.html"
|
||
}
|
||
}
|
||
```
|
||
|
||
### `content_scripts`
|
||
|
||
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).
|
||
|
||
```json
|
||
"content_scripts": [
|
||
{
|
||
"js": [
|
||
"script.js"
|
||
],
|
||
"matches": [
|
||
"https://example.com/*",
|
||
"https://www.example.com/*"
|
||
],
|
||
"exclude_matches": ["*://*/*business*"],
|
||
}
|
||
],
|
||
```
|
||
|
||
In order to include or exclude more URLs it's also possible to use **`include_globs`** and **`exclude_globs`**.
|
||
|
||
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.
|
||
|
||
```js
|
||
chrome.storage.local.get("message", result =>
|
||
{
|
||
let div = document.createElement("div");
|
||
div.innerHTML = result.message + " <button>Explain</button>";
|
||
div.querySelector("button").addEventListener("click", () =>
|
||
{
|
||
chrome.runtime.sendMessage("explain");
|
||
});
|
||
document.body.appendChild(div);
|
||
});
|
||
```
|
||
|
||
<figure><img src="../../.gitbook/assets/image (23).png" alt=""><figcaption></figcaption></figure>
|
||
|
||
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.
|
||
|
||
{% hint style="warning" %}
|
||
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.\
|
||
It is also noteworthy that content scripts have the ability to communicate with background scripts, enabling them to perform actions and relay responses back.
|
||
{% endhint %}
|
||
|
||
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.
|
||
|
||
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.
|
||
|
||
### Injected content scripts
|
||
|
||
{% hint style="success" %}
|
||
Note that **Content Scripts aren't mandatory** as it's also possible to **dynamically** **inject** scripts and to **programatically inject them** in web pages via **`tabs.executeScript`**. This actually provides more **granular controls**.
|
||
{% endhint %}
|
||
|
||
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).
|
||
|
||
#### Example activeTab-based extension
|
||
|
||
{% code title="manifest.json" %}
|
||
```json
|
||
{
|
||
"name": "My extension",
|
||
...
|
||
"permissions": [
|
||
"activeTab",
|
||
"scripting"
|
||
],
|
||
"background": {
|
||
"service_worker": "background.js"
|
||
},
|
||
"action": {
|
||
"default_title": "Action Button"
|
||
}
|
||
}
|
||
```
|
||
{% endcode %}
|
||
|
||
* **Inject a JS file on click:**
|
||
|
||
```javascript
|
||
// content-script.js
|
||
document.body.style.backgroundColor = "orange";
|
||
|
||
//service-worker.js - Inject the JS file
|
||
chrome.action.onClicked.addListener((tab) => {
|
||
chrome.scripting.executeScript({
|
||
target: { tabId: tab.id },
|
||
files: ["content-script.js"]
|
||
});
|
||
});
|
||
```
|
||
|
||
* **Inject a function** on click:
|
||
|
||
```javascript
|
||
//service-worker.js - Inject a function
|
||
function injectedFunction() {
|
||
document.body.style.backgroundColor = "orange";
|
||
}
|
||
|
||
chrome.action.onClicked.addListener((tab) => {
|
||
chrome.scripting.executeScript({
|
||
target : {tabId : tab.id},
|
||
func : injectedFunction,
|
||
});
|
||
});
|
||
```
|
||
|
||
#### Example with scripting permissions
|
||
|
||
```javascript
|
||
// service-workser.js
|
||
chrome.scripting.registerContentScripts([{
|
||
id : "test",
|
||
matches : [ "https://*.example.com/*" ],
|
||
excludeMatches : [ "*://*/*business*" ],
|
||
js : [ "contentScript.js" ],
|
||
}]);
|
||
|
||
// Another example
|
||
chrome.tabs.executeScript(tabId, { file: "content_script.js" });
|
||
```
|
||
|
||
In order to include or exclude more URLs it's also possible to use **`include_globs`** and **`exclude_globs`**.
|
||
|
||
### Content Scripts `run_at`
|
||
|
||
The `run_at` field controls **when JavaScript files are injected into the web page**. The preferred and default value is `"document_idle"`.
|
||
|
||
The possible values are:
|
||
|
||
* **`document_idle`**: Whenever possible
|
||
* **`document_start`**: After any files from `css`, but before any other DOM is constructed or any other script is run.
|
||
* **`document_end`**: Immediately after the DOM is complete, but before subresources like images and frames have loaded.
|
||
|
||
#### Via `manifest.json`
|
||
|
||
```json
|
||
{
|
||
"name": "My extension",
|
||
...
|
||
"content_scripts": [
|
||
{
|
||
"matches": ["https://*.example.com/*"],
|
||
"run_at": "document_idle",
|
||
"js": ["contentScript.js"]
|
||
}
|
||
],
|
||
...
|
||
}
|
||
|
||
```
|
||
|
||
Via **`service-worker.js`**
|
||
|
||
```javascript
|
||
chrome.scripting.registerContentScripts([{
|
||
id : "test",
|
||
matches : [ "https://*.example.com/*" ],
|
||
runAt : "document_idle",
|
||
js : [ "contentScript.js" ],
|
||
}]);
|
||
```
|
||
|
||
### `background`
|
||
|
||
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.
|
||
|
||
**Key Points**:
|
||
|
||
* **Background Page Role:** Acts as the nerve center for the extension, ensuring communication and coordination among various parts of the extension.
|
||
* **Persistence:** It's an ever-present entity, invisible to the user but integral to the extension's functionality.
|
||
* **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.
|
||
|
||
{% hint style="success" %}
|
||
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.
|
||
{% endhint %}
|
||
|
||
Example background script:
|
||
|
||
```js
|
||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) =>
|
||
{
|
||
if (request == "explain")
|
||
{
|
||
chrome.tabs.create({ url: "https://example.net/explanation" });
|
||
}
|
||
})
|
||
```
|
||
|
||
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.
|
||
|
||
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:
|
||
|
||
<figure><img src="https://github.com/carlospolop/hacktricks/blob/master/pentesting-web/browser-extension-pentesting-methodology/broken-reference" alt=""><figcaption></figcaption></figure>
|
||
|
||
### Options pages and other
|
||
|
||
Browser extensions can contain various kinds of pages:
|
||
|
||
* **Action pages** are displayed in a **drop-down when the extension ico**n is clicked.
|
||
* Pages that the extension will **load in a new tab**.
|
||
* **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:
|
||
|
||
<figure><img src="../../.gitbook/assets/image (24).png" alt="" width="375"><figcaption></figcaption></figure>
|
||
|
||
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:
|
||
|
||
* **Communication with Content Scripts:** Similar to the background page, these pages can receive messages from content scripts, facilitating interaction within the extension.
|
||
* **Access to Extension-Specific APIs:** These pages enjoy comprehensive access to extension-specific APIs, subject to the permissions defined for the extension.
|
||
|
||
### `permissions` & `host_permissions`
|
||
|
||
**`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**.
|
||
|
||
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**.
|
||
|
||
Check how these settings work and how they could get abused in:
|
||
|
||
{% content-ref url="browext-permissions-and-host_permissions.md" %}
|
||
[browext-permissions-and-host\_permissions.md](browext-permissions-and-host_permissions.md)
|
||
{% endcontent-ref %}
|
||
|
||
### `content_security_policy`
|
||
|
||
A **content security policy** can be declared also inside the `manifest.json`. If there is one defined, it could be **vulnerable**.
|
||
|
||
The default setting for browser extension pages is rather restrictive:
|
||
|
||
```bash
|
||
script-src 'self'; object-src 'self';
|
||
```
|
||
|
||
For more info about CSP and potential bypasses check:
|
||
|
||
{% content-ref url="../content-security-policy-csp-bypass/" %}
|
||
[content-security-policy-csp-bypass](../content-security-policy-csp-bypass/)
|
||
{% endcontent-ref %}
|
||
|
||
### `web_accessible_resources`
|
||
|
||
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`.\
|
||
For example:
|
||
|
||
```javascript
|
||
{
|
||
...
|
||
"web_accessible_resources": [
|
||
{
|
||
"resources": [ "images/*.png" ],
|
||
"matches": [ "https://example.com/*" ]
|
||
},
|
||
{
|
||
"resources": [ "fonts/*.woff" ],
|
||
"matches": [ "https://example.com/*" ]
|
||
}
|
||
],
|
||
...
|
||
}
|
||
```
|
||
|
||
These pages are accesible in URL like:
|
||
|
||
```
|
||
chrome-extension://<extension-id>/message.html
|
||
```
|
||
|
||
In public extensions the **extension-id is accesible**:
|
||
|
||
<figure><img src="../../.gitbook/assets/image (1194).png" alt="" width="375"><figcaption></figcaption></figure>
|
||
|
||
Although, if the `manifest.json` parameter **`use_dynamic_url`** is used, this **id can be dynamic**.
|
||
|
||
{% hint style="success" %}
|
||
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.
|
||
{% endhint %}
|
||
|
||
Being allowed to access these pages make these pages **potentially vulnerable ClickJacking**:
|
||
|
||
{% content-ref url="browext-clickjacking.md" %}
|
||
[browext-clickjacking.md](browext-clickjacking.md)
|
||
{% endcontent-ref %}
|
||
|
||
{% hint style="success" %}
|
||
Allowing these pages to be loaded only by the extension and not by random URLs could prevent ClickJacking attacks.
|
||
{% endhint %}
|
||
|
||
{% hint style="danger" %}
|
||
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.
|
||
|
||
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`**.
|
||
{% endhint %}
|
||
|
||
### `externally_connectable`
|
||
|
||
A per the [**docs**](https://developer.chrome.com/docs/extensions/reference/manifest/externally-connectable), 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).
|
||
|
||
* 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**.
|
||
* If **specific IDs are specified**, like in `"ids": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]`, **only those applications** can connect.
|
||
* If **matches** are specified, those web apps will be able to connect:
|
||
|
||
```json
|
||
"matches": [
|
||
"https://*.google.com/*",
|
||
"*://*.chromium.org/*",
|
||
```
|
||
|
||
* If it's specified as empty: **`"externally_connectable": {}`**, no app or web will be able to connect.
|
||
|
||
The **less extensions and URLs** indicated here, the **smaller the attack surface** will be.
|
||
|
||
{% hint style="danger" %}
|
||
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.
|
||
|
||
Therefore, this is a **very powerful bypass**.
|
||
|
||
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).
|
||
{% endhint %}
|
||
|
||
## Communication summary
|
||
|
||
### Extension <--> WebApp
|
||
|
||
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.
|
||
|
||
### Inside the extension
|
||
|
||
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`**.
|
||
|
||
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:
|
||
|
||
<details>
|
||
|
||
<summary><code>chrome.runtime.connect()</code> example</summary>
|
||
|
||
```javascript
|
||
var port = chrome.runtime.connect();
|
||
|
||
// Listen for messages from the web page
|
||
window.addEventListener("message", (event) => {
|
||
// Only accept messages from the same window
|
||
if (event.source !== window) {
|
||
return;
|
||
}
|
||
|
||
// Check if the message type is "FROM_PAGE"
|
||
if (event.data.type && (event.data.type === "FROM_PAGE")) {
|
||
console.log("Content script received: " + event.data.text);
|
||
// Forward the message to the background script
|
||
port.postMessage({ type: 'FROM_PAGE', text: event.data.text });
|
||
}
|
||
}, false);
|
||
|
||
// Listen for messages from the background script
|
||
port.onMessage.addListener(function(msg) {
|
||
console.log("Content script received message from background script:", msg);
|
||
// Handle the response message from the background script
|
||
});
|
||
```
|
||
|
||
</details>
|
||
|
||
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.
|
||
|
||
### From allowed `externally_connectable` to the extension
|
||
|
||
**Web apps and external browser extensions allowed** in the `externally_connectable` configuration can send requests using :
|
||
|
||
```javascript
|
||
chrome.runtime.sendMessage(extensionId, ...
|
||
```
|
||
|
||
Where it's needed to mention the **extension ID**.
|
||
|
||
### Native Messaging
|
||
|
||
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).
|
||
|
||
```javascript
|
||
chrome.runtime.sendNativeMessage(
|
||
'com.my_company.my_application',
|
||
{text: 'Hello'},
|
||
function (response) {
|
||
console.log('Received ' + response);
|
||
}
|
||
);
|
||
```
|
||
|
||
## Web **↔︎** Content Script Communication
|
||
|
||
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.
|
||
|
||
### Post Messages
|
||
|
||
{% code title="content-script.js" %}
|
||
```javascript
|
||
// This is like "chrome.runtime.sendMessage" but to maintain the connection
|
||
var port = chrome.runtime.connect();
|
||
|
||
window.addEventListener("message", (event) => {
|
||
// We only accept messages from ourselves
|
||
if (event.source !== window) {
|
||
return;
|
||
}
|
||
|
||
if (event.data.type && (event.data.type === "FROM_PAGE")) {
|
||
console.log("Content script received: " + event.data.text);
|
||
// Forward the message to the background script
|
||
port.postMessage(event.data.text);
|
||
}
|
||
}, false);
|
||
```
|
||
{% endcode %}
|
||
|
||
{% code title="example.js" %}
|
||
```javascript
|
||
document.getElementById("theButton").addEventListener("click", () => {
|
||
window.postMessage(
|
||
{type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
|
||
}, false);
|
||
```
|
||
{% endcode %}
|
||
|
||
A secure Post Message communication should check the authenticity of the received message, this can be done checking:
|
||
|
||
* **`event.isTrusted`**: This is True only if the event was triggered by a users action
|
||
* The content script might expecting a message only if the user performs some action
|
||
* **origin domain**: might expecting a message only allowlist of domains.
|
||
* If a regex is used, be very careful
|
||
* **Source**: `received_message.source !== window` can be used to check if the message was **from the same window** where the Content Script is listening.
|
||
|
||
The previous checks, even if performed, could be vulnerable, so check in the following page **potential Post Message bypasses**:
|
||
|
||
{% content-ref url="../postmessage-vulnerabilities/" %}
|
||
[postmessage-vulnerabilities](../postmessage-vulnerabilities/)
|
||
{% endcontent-ref %}
|
||
|
||
### Iframe
|
||
|
||
Another possible way of communication might be through **Iframe URLs**, you can find an example in:
|
||
|
||
{% content-ref url="browext-xss-example.md" %}
|
||
[browext-xss-example.md](browext-xss-example.md)
|
||
{% endcontent-ref %}
|
||
|
||
### DOM
|
||
|
||
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**.
|
||
|
||
You can also find an example of a **DOM based XSS to compromise a browser extension** in:
|
||
|
||
{% content-ref url="browext-xss-example.md" %}
|
||
[browext-xss-example.md](browext-xss-example.md)
|
||
{% endcontent-ref %}
|
||
|
||
## Content Script **↔︎** Background Script Communication
|
||
|
||
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.
|
||
|
||
To handle the **response**, use the returned **Promise**. Although, for backward compatibility, you can still pass a **callback** as the last argument.
|
||
|
||
Sending a request from a **content script** looks like this:
|
||
|
||
```javascript
|
||
(async () => {
|
||
const response = await chrome.runtime.sendMessage({greeting: "hello"});
|
||
// do something with response here, not outside the function
|
||
console.log(response);
|
||
})();
|
||
```
|
||
|
||
Sending a request from the **extension** (usually a **background script**). Example of how to send message to the content script in the selected tab:
|
||
|
||
```javascript
|
||
// From https://stackoverflow.com/questions/36153999/how-to-send-a-message-between-chrome-extension-popup-and-content-script
|
||
(async () => {
|
||
const [tab] = await chrome.tabs.query({active: true, lastFocusedWindow: true});
|
||
const response = await chrome.tabs.sendMessage(tab.id, {greeting: "hello"});
|
||
// do something with response here, not outside the function
|
||
console.log(response);
|
||
})();
|
||
```
|
||
|
||
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.
|
||
|
||
```javascript
|
||
// From https://stackoverflow.com/questions/70406787/javascript-send-message-from-content-js-to-background-js
|
||
chrome.runtime.onMessage.addListener(
|
||
function(request, sender, sendResponse) {
|
||
console.log(sender.tab ?
|
||
"from a content script:" + sender.tab.url :
|
||
"from the extension");
|
||
if (request.greeting === "hello")
|
||
sendResponse({farewell: "goodbye"});
|
||
}
|
||
);
|
||
```
|
||
|
||
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;`.
|
||
|
||
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.
|
||
|
||
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.
|
||
|
||
## Native Messaging
|
||
|
||
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:
|
||
|
||
```json
|
||
{
|
||
"name": "com.my_company.my_application",
|
||
"description": "My Application",
|
||
"path": "C:\\Program Files\\My Application\\chrome_native_messaging_host.exe",
|
||
"type": "stdio",
|
||
"allowed_origins": ["chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"]
|
||
}
|
||
```
|
||
|
||
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).
|
||
|
||
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)).
|
||
|
||
{% hint style="success" %}
|
||
The browser extension also needs the `nativeMessaing` permission declared in order to be able to use this communication.
|
||
{% endhint %}
|
||
|
||
This is how it looks like some background script code sending messages to a native application:
|
||
|
||
```javascript
|
||
chrome.runtime.sendNativeMessage(
|
||
'com.my_company.my_application',
|
||
{text: 'Hello'},
|
||
function (response) {
|
||
console.log('Received ' + response);
|
||
}
|
||
);
|
||
```
|
||
|
||
In [**this blog post**](https://spaceraccoon.dev/universal-code-execution-browser-extensions/), a vulnerable pattern abusing native messages is proposed:
|
||
|
||
1. Browser extension has a wildcard pattern for content script.
|
||
2. Content script passes `postMessage` messages to the background script using `sendMessage`.
|
||
3. Background script passes the message to native application using `sendNativeMessage`.
|
||
4. Native application handles the message dangerously, leading to code execution.
|
||
|
||
And inside of it an example of **going from any page to RCE abusing a browser extension is explained**.
|
||
|
||
## Sensitive Information in Memory/Code/Clipboard
|
||
|
||
If a Browser Extension stores **sensitive information inside it's memory**, this could be **dumped** (specially in Windows machines) and **searched** for this information.
|
||
|
||
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**.
|
||
|
||
Of course, do **not put sensitive information in the code**, as it will be **public**.
|
||
|
||
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.
|
||
|
||
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.
|
||
|
||
## Loading an Extension in the Browser
|
||
|
||
1. **Download** the Browser Extension & unzipped
|
||
2. Go to **`chrome://extensions/`** and **enable** the `Developer Mode`
|
||
3. Click the **`Load unpacked`** button
|
||
|
||
In **Firefox** you go to **`about:debugging#/runtime/this-firefox`** and click **`Load Temporary Add-on`** button.
|
||
|
||
## Getting the source code from the store
|
||
|
||
The source code of a Chrome extension can be obtained through various methods. Below are detailed explanations and instructions for each option.
|
||
|
||
### Download Extension as ZIP via Command Line
|
||
|
||
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:
|
||
|
||
1. Replace `"extension_id"` with the actual ID of the extension.
|
||
2. Execute the following commands:
|
||
|
||
```bash
|
||
extension_id=your_extension_id # Replace with the actual extension ID
|
||
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"
|
||
unzip -d "$extension_id-source" "$extension_id.zip"
|
||
```
|
||
|
||
### Use the CRX Viewer website
|
||
|
||
[https://robwu.nl/crxviewer/](https://robwu.nl/crxviewer/)
|
||
|
||
### Use the CRX Viewer extension
|
||
|
||
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).
|
||
|
||
### View source of locally installed extension
|
||
|
||
Chrome extensions installed locally can also be inspected. Here's how:
|
||
|
||
1. Access your Chrome local profile directory by visiting `chrome://version/` and locating the "Profile Path" field.
|
||
2. Navigate to the `Extensions/` subfolder within the profile directory.
|
||
3. This folder contains all installed extensions, typically with their source code in a readable format.
|
||
|
||
To identify extensions, you can map their IDs to names:
|
||
|
||
* Enable Developer Mode on the `about:extensions` page to see the IDs of each extension.
|
||
* Within each extension's folder, the `manifest.json` file contains a readable `name` field, helping you to identify the extension.
|
||
|
||
### Use a File Archiver or Unpacker
|
||
|
||
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.
|
||
|
||
### Use Developer Mode in Chrome
|
||
|
||
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.
|
||
|
||
## Chrome extension manifest dataset
|
||
|
||
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`:
|
||
|
||
{% code overflow="wrap" %}
|
||
```bash
|
||
# Query example from https://spaceraccoon.dev/universal-code-execution-browser-extensions/
|
||
node query.js -f "metadata.user_count > 250000" "manifest.content_scripts?.length > 0 && manifest.permissions?.includes('nativeMessaging')"
|
||
```
|
||
{% endcode %}
|
||
|
||
## Security Audit Checklist
|
||
|
||
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:
|
||
|
||
* [ ] **Limit** as much as possible requested **`permissions`**
|
||
* [ ] **Limit** as much as possible **`host_permissions`**
|
||
* [ ] Use a **strong** **`content_security_policy`**
|
||
* [ ] **Limit** as much as possible the **`externally_connectable`**, if none is needed and possible, do not leave it by default, specify **`{}`**
|
||
* [ ] 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.
|
||
* [ ] **Limit** as much as possible the **`web_accessible_resources`**, even empty if possible.
|
||
* [ ] If **`web_accessible_resources`** is not none, check for [**ClickJacking**](browext-clickjacking.md)
|
||
* [ ] If any **communication** occurs from the **extension** to the **web page**, [**check for XSS**](browext-xss-example.md) **vulnerabilities** caused in the communication.
|
||
* [ ] If Post Messages are used, check for [**Post Message vulnerabilities**](../postmessage-vulnerabilities/)**.**
|
||
* [ ] If the **Content Script access DOM details**, check that they **aren't introducing a XSS** if they get **modified** by the web
|
||
* [ ] Make a special emphasis if this communication is also involved in the **Content Script -> Background script communication**
|
||
* [ ] If the background script is communicating via **native messaging** check the communication is secure and sanitized
|
||
* [ ] **Sensitive information shouldn't be stored** inside the Browser Extension **code**
|
||
* [ ] **Sensitive information shouldn't be stored** inside the Browser Extension **memory**
|
||
* [ ] **Sensitive information shouldn't be stored** inside the **file system unprotected**
|
||
|
||
## Browser Extension Risks
|
||
|
||
* 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.
|
||
|
||
## Tools
|
||
|
||
### [**Tarnish**](https://thehackerblog.com/tarnish/)
|
||
|
||
* Pulls any Chrome extension from a provided Chrome webstore link.
|
||
* [**manifest.json**](https://developer.chrome.com/extensions/manifest) **viewer**: simply displays a JSON-prettified version of the extension’s manifest.
|
||
* **Fingerprint Analysis**: Detection of [web\_accessible\_resources](https://developer.chrome.com/extensions/manifest/web_accessible_resources) and automatic generation of Chrome extension fingerprinting JavaScript.
|
||
* **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.
|
||
* **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.
|
||
* **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).
|
||
* **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.
|
||
* Both the Dangerous Function(s) and Entry Point(s) scanners have the following for their generated alerts:
|
||
* Relevant code snippet and line that caused the alert.
|
||
* Description of the issue.
|
||
* A “View File” button to view the full source file containing the code.
|
||
* The path of the alerted file.
|
||
* The full Chrome extension URI of the alerted file.
|
||
* The type of file it is, such as a Background Page script, Content Script, Browser Action, etc.
|
||
* 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.
|
||
* **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.
|
||
* **Known Vulnerable Libraries**: This uses [Retire.js](https://retirejs.github.io/retire.js/) to check for any usage of known-vulnerable JavaScript libraries.
|
||
* Download extension and formatted versions.
|
||
* Download the original extension.
|
||
* Download a beautified version of the extension (auto prettified HTML and JavaScript).
|
||
* 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.
|
||
* Linkable Report URLs, easily link someone else to an extension report generated by tarnish.
|
||
|
||
### [Neto](https://github.com/elevenpaths/neto)
|
||
|
||
Project Neto is a Python 3 package conceived to analyse and unravel hidden features of browser plugins and extensions for well-known browsers such as Firefox and Chrome. It automates the process of unzipping the packaged files to extract these features from relevant resources in a extension like `manifest.json`, localization folders or Javascript and HTML source files.
|
||
|
||
## References
|
||
|
||
* **Thanks to** [**@naivenom**](https://twitter.com/naivenom) **for the help with this methodology**
|
||
* [https://www.cobalt.io/blog/introduction-to-chrome-browser-extension-security-testing](https://www.cobalt.io/blog/introduction-to-chrome-browser-extension-security-testing)
|
||
* [https://palant.info/2022/08/10/anatomy-of-a-basic-extension/](https://palant.info/2022/08/10/anatomy-of-a-basic-extension/)
|
||
* [https://palant.info/2022/08/24/attack-surface-of-extension-pages/](https://palant.info/2022/08/24/attack-surface-of-extension-pages/)
|
||
* [https://palant.info/2022/08/31/when-extension-pages-are-web-accessible/](https://palant.info/2022/08/31/when-extension-pages-are-web-accessible/)
|
||
* [https://help.passbolt.com/assets/files/PBL-02-report.pdf](https://help.passbolt.com/assets/files/PBL-02-report.pdf)
|
||
* [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/mv2/background-pages](https://developer.chrome.com/docs/extensions/mv2/background-pages)
|
||
* [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)
|
||
|
||
{% hint style="success" %}
|
||
Learn & practice AWS Hacking:<img src="../../.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../../.gitbook/assets/arte.png" alt="" data-size="line">\
|
||
Learn & practice GCP Hacking: <img src="../../.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="../../.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
||
|
||
<details>
|
||
|
||
<summary>Support HackTricks</summary>
|
||
|
||
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
|
||
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks_live)**.**
|
||
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
|
||
|
||
</details>
|
||
{% endhint %}
|