浏览器扩展 Pentesting 方法论

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

基本信息

浏览器扩展使用 JavaScript 编写,并由浏览器在后台加载。它有其 DOM,但可以与其他站点的 DOM 交互。这意味着它可能影响其他站点的机密性、完整性和可用性 (CIA)。

主要组件

将扩展的布局可视化最为直观,通常由三个组件组成。下面深入查看每个组件。

http://webblaze.cs.berkeley.edu/papers/Extensions.pdf

内容脚本

每个内容脚本可以直接访问单个网页的 DOM,因此会暴露于 潜在的恶意输入。但内容脚本本身除了向扩展核心发送消息外没有其它权限。

扩展核心

扩展核心包含扩展的大部分权限/访问,但扩展核心只能通过 XMLHttpRequest 和内容脚本与 Web 内容交互。另外,扩展核心没有对主机的直接访问权限。

本地二进制

扩展可以包含一个本地二进制程序,能够以用户的全部权限访问主机。该本地二进制通过标准的 Netscape Plugin Application Programming Interface (NPAPI) 与扩展核心交互,该接口曾被 Flash 和其他浏览器插件使用。

Boundaries

Caution

要获得用户的全部权限,攻击者必须说服扩展将恶意输入从内容脚本传递到扩展核心,并且从扩展核心传递到本地二进制程序。

扩展的每个组件之间由 强大的保护边界 隔离。每个组件运行在 单独的操作系统进程 中。内容脚本和扩展核心运行在多数操作系统服务无法访问的 沙箱进程 中。

此外,内容脚本通过 运行在单独的 JavaScript 堆中 与其关联网页分离。内容脚本和网页可以访问相同的底层 DOM,但两者绝不会交换 JavaScript 指针,从而防止 JavaScript 功能的 leak。

manifest.json

Chrome 扩展本质上只是一个 ZIP 文件夹,带有 .crx file extension。扩展的核心是位于文件夹根目录的 manifest.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 在用户导航到匹配的页面被加载,在我们的例子中,任何匹配 https://example.com/* 表达式且不匹配 *://*/*/business* 正则的页面。它们像页面自身的脚本一样执行,并对页面的 Document Object Model (DOM) 拥有任意访问权限。

"content_scripts": [
{
"js": [
"script.js"
],
"matches": [
"https://example.com/*",
"https://www.example.com/*"
],
"exclude_matches": ["*://*/*business*"],
}
],

为了包含或排除更多 URLs,也可以使用 include_globsexclude_globs

下面是一个示例 content script,当使用 the storage API 从扩展的存储中检索 message 值时,它会向页面添加一个 explain 按钮。

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)
})

当点击此按钮时,内容脚本会通过 runtime.sendMessage() API 向扩展页面发送消息。这是因为内容脚本在直接访问大多数 API 时受到限制,storage 是为数不多的例外之一。对于这些例外以外的功能,消息会被发送到扩展页面,内容脚本可以与这些页面通信。

Warning

取决于浏览器,内容脚本的能力可能会略有不同。对于 Chromium-based browsers,功能列表可在 Chrome Developers documentation 中找到,而对于 Firefox, MDN 则是主要参考来源。
还值得注意的是,内容脚本可以与后台脚本通信,从而执行操作并回传响应。

要在 Chrome 中查看和调试内容脚本,可通过 Options > More tools > Developer tools 打开 Chrome developer tools,或按 Ctrl + Shift + I。

打开 developer tools 后,点击 Source 选项卡,然后选择 Content Scripts 选项卡。这样可以查看来自各个扩展的运行中内容脚本,并设置断点以追踪执行流程。

注入的内容脚本

Tip

请注意,内容脚本并非必须,也可以动态注入脚本,或通过 tabs.executeScript编程方式注入到网页中。这样实际上可以提供更细粒度的控制

要以编程方式注入内容脚本,扩展需要对要注入脚本的页面拥有 host permissions。这些权限可以通过在扩展的 manifest 中 请求它们 来获取,或通过 activeTab 临时授予。

示例 activeTab-based 扩展

{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
  • 在点击时注入一个 JS 文件:
// 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"],
})
})
  • 在点击时注入一个函数:
//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,
})
})

具有脚本权限的示例

// 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" })

为了包含或排除更多 URLs,也可以使用 include_globsexclude_globs

内容脚本 run_at

字段 run_at 控制 何时将 JavaScript 文件注入到网页中。推荐且默认值为 "document_idle"

可能的值包括:

  • document_idle: 在可能的情况下
  • document_start: 在 css 的任何文件之后,但在构建任何其他 DOM 或运行任何其他脚本之前。
  • document_end: 在 DOM 完成之后立即执行,但在诸如图片和 frames 的子资源加载之前。

通过 manifest.json

{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.example.com/*"],
"run_at": "document_idle",
"js": ["contentScript.js"]
}
],
...
}

通过 service-worker.js

chrome.scripting.registerContentScripts([
{
id: "test",
matches: ["https://*.example.com/*"],
runAt: "document_idle",
js: ["contentScript.js"],
},
])

background

由 content scripts 发送的消息会被 后台页面 接收,后台页面 在协调扩展各组件方面起着核心作用。值得注意的是,后台页面在扩展的整个生命周期内保持存在,默默运行且无需直接用户交互。它拥有自己的文档对象模型 (DOM),从而能够实现复杂的交互和状态管理。

关键点

  • 后台页面的作用: 作为扩展的神经中枢,确保扩展各部分之间的通信与协调。
  • 持久性: 它是一个始终存在的实体,对用户不可见,但对扩展功能至关重要。
  • 自动生成: 如果未显式定义,浏览器会自动创建后台页面。该自动生成的页面会包含扩展 manifest 中指定的所有后台脚本,从而确保扩展后台任务的无缝运行。

Tip

浏览器在未显式声明时自动生成后台页面的便利性,确保了所有必要的后台脚本都被集成并可运行,从而简化了扩展的设置流程。

示例后台脚本:

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request == "explain") {
chrome.tabs.create({ url: "https://example.net/explanation" })
}
})

It uses runtime.onMessage API to listen to messages. When an "explain" message is received, it uses tabs API to open a page in a new tab.

要调试背景脚本,你可以转到 extension details and inspect the service worker, 这会打开包含背景脚本的开发者工具:

Options pages and other

Browser extensions can contain various kinds of pages:

  • Action pages are displayed in a drop-down when the extension icon 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:

注意这些页面不像背景页那样持久,它们会根据需要动态加载内容。尽管如此,它们与背景页共享某些能力:

  • Communication with Content Scripts: 与背景页类似,这些页面可以接收来自内容脚本的消息,从而促进扩展内部的交互。
  • Access to Extension-Specific APIs: 这些页面可以全面访问扩展特定的 API,前提是扩展为这些 API 定义了相应的 permissions。

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.

由于 browser extensions 可能非常privileged,恶意扩展或被攻陷的扩展可能会允许攻击者通过多种方式窃取敏感信息并监视用户

查看这些设置如何工作以及它们如何被滥用,请参阅:

BrowExt - permissions & host_permissions

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:

script-src 'self'; object-src 'self';

有关 CSP 及可能的绕过方法的更多信息,请查看:

Content Security Policy (CSP) Bypass

web_accessible_resources

为了让网页访问浏览器扩展的某个页面,例如一个 .html 页面,该页面需要在 manifest.jsonweb_accessible_resources 字段中被列出。
例如:

{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}

这些页面可以通过如下 URL 访问:

chrome-extension://<extension-id>/message.html

在公共扩展中,extension-id 是可访问的

Although, if the manifest.json parameter use_dynamic_url is used, this id can be dynamic.

Tip

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.

允许访问这些页面会使这些页面可能易受 ClickJacking 攻击

BrowExt - ClickJacking

Tip

Allowing these pages to be loaded only by the extension and not by random URLs could prevent ClickJacking attacks.

Caution

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.

externally_connectable

A per the docs, The "externally_connectable" manifest property declares which extensions and web pages can connect to your extension via runtime.connect and runtime.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:
"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
  • 如果指定为空:"externally_connectable": {},则没有任何应用或网站能够连接。

这里指定的扩展和 URL 越少,攻击面就越小。

Caution

如果在 externally_connectable 中指明了一个对 XSS 或 takeover 易受攻击的网页,攻击者将能够 直接向 background script 发送消息,完全绕过 Content Script 及其 CSP。

因此,这是一个非常强大的 bypass

此外,如果客户端安装了一个 rouge extension,即使它不被允许与易受攻击的扩展通信,它也可能在被允许的网页中注入 XSS data,或滥用 WebRequestDeclarativeNetRequest APIs 来操纵目标域上的请求,改变页面对 JavaScript file 的请求。(注意目标页面上的 CSP 可能会阻止这些攻击)。这个想法来自 from this writeup.

通信摘要

扩展 <–> WebApp

在 content script 与网页之间进行通信时,通常使用 post messages。因此,在 web application 中你通常会看到对函数 window.postMessage 的调用,而在 content script 中会看到像 window.addEventListener 这样的监听器。注意,extension 也可以通过发送 Post Message 与 web application 通信(因此网页应当预期到这种情况),或者只是让网页加载一个新的脚本。

扩展内部

通常使用函数 chrome.runtime.sendMessage 在扩展内部发送消息(通常由 background script 处理),为了接收并处理该消息,会声明一个监听器,调用 chrome.runtime.onMessage.addListener

也可以使用 chrome.runtime.connect() 来建立一个持久连接,而不是发送单次消息。可以使用它来 发送接收 消息,例如下面的示例:

chrome.runtime.connect() example ```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>

也可以从 background script 向位于特定 tab 的 content script 发送消息,调用 **`chrome.tabs.sendMessage`**,此时需要指定要发送消息的**tab ID**。

### 从 `externally_connectable` 允许的来源到扩展

**配置中 `externally_connectable` 允许的 Web apps 和外部浏览器扩展** 可以使用以下方式发送请求:
```javascript
chrome.runtime.sendMessage(extensionId, ...

在需要提到 extension ID 的地方。

Native Messaging

background scripts 可以与系统内的 binaries 通信,如果这种通信没有得到妥善保护,可能会导致严重漏洞(例如 RCEs)More on this later.

chrome.runtime.sendNativeMessage(
"com.my_company.my_application",
{ text: "Hello" },
function (response) {
console.log("Received " + response)
}
)

Web ↔︎ Content Script Communication

运行 content scripts 的环境与宿主页面所在的环境是相互隔离的,从而确保了孤立性。尽管存在这种隔离,双方都能与页面的 文档对象模型 (DOM) 交互——这是一个共享资源。为了让宿主页面与 content script 通信,或通过 content script 间接与扩展进行通信,必须使用双方都可访问的 DOM 作为通信通道。

Post Messages

// 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
)
document.getElementById("theButton").addEventListener(
"click",
() => {
window.postMessage(
{ type: "FROM_PAGE", text: "Hello from the webpage!" },
"*"
)
},
false
)

一个安全的 Post Message 通信应当验证接收消息的真实性,可以通过检查:

  • event.isTrusted: 只有当事件由用户动作触发时才为 True
  • content script 可能只在用户执行某些操作时才期待消息
  • origin domain: 可能只允许来自 allowlist 的域名。
  • 如果使用正则表达式,请非常小心
  • Source: received_message.source !== window 可用于检查消息是否来自 Content Script 正在监听的同一窗口。

即使进行了上述检查,也可能存在漏洞,请参阅下列页面中的 潜在的 Post Message 绕过方法

PostMessage Vulnerabilities

Iframe

另一种可能的通信方式是通过 Iframe URLs,示例见:

BrowExt - XSS Example

DOM

这并不“完全”是通信方式,但 web 和 content script 都能访问 web DOM。因此,如果 content script 从中读取某些信息、并且 信任 web DOM,web 端可能会修改这些数据(因为 web 不应被信任,或 web 易受 XSS 攻击),从而 危及 Content Script

你也可以在以下链接找到一个通过基于 DOM 的 XSS 来攻陷浏览器扩展的示例:

BrowExt - XSS Example

Content Script ↔︎ Background Script Communication

A Content Script can use the functions runtime.sendMessage() or tabs.sendMessage() to send a one-time JSON-serializable message.

处理 response 时,使用返回的 Promise。不过,为了向后兼容,你仍然可以把 callback 作为最后一个参数传入。

content script 发送请求看起来像这样:

;(async () => {
const response = await chrome.runtime.sendMessage({ greeting: "hello" })
// do something with response here, not outside the function
console.log(response)
})()

extension(通常是 background script)发送请求。下面示例演示如何向所选 tab 中的 content script 发送消息:

// 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)
})()

接收端,你需要设置一个 runtime.onMessage 事件监听器 来处理消息。在 content script 或 extension page 中,这看起来是相同的。

// 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;.

在上例中,sendResponse() 是以同步方式执行的。若要将 onMessage 事件处理程序修改为异步执行 sendResponse(),必须加入 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.

重要的一点是,如果有多个页面设置为接收 onMessage 事件,第一个为某个事件执行 sendResponse() 的页面 将是唯一能成功发送响应的页面。对同一事件的后续响应将被忽略。

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.

在开发新扩展时,应优先使用 promises 而非 callbacks。关于回调的使用,只有在 sendResponse() 在同步上下文中直接执行,或者事件处理程序通过返回 true 表明进行异步操作时,该 sendResponse() 函数才有效。如果没有任何处理程序返回 true,或 sendResponse() 被移出内存(被垃圾回收),与 sendMessage() 关联的回调将默认被触发。

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:

浏览器扩展还允许与 binaries in the system via stdin 通信。应用必须安装一个 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/"]
}

其中 name 是传递给 runtime.connectNative()runtime.sendNativeMessage() 的字符串,用于从浏览器扩展的后台脚本与该应用通信。path 是二进制文件的路径,type 只有 1 个有效值,即 stdio(使用 stdin 和 stdout),allowed_origins 指示可以访问它的扩展(不能使用通配符)。

Chrome/Chromium 会在 Windows 注册表的某些位置以及 macOS 和 Linux 的某些路径中搜索该 json(更多信息见 docs)。

Tip

浏览器扩展还需要声明 nativeMessaing 权限,才能使用此通信。

下面是后台脚本向本地应用发送消息的示例代码:

chrome.runtime.sendNativeMessage(
"com.my_company.my_application",
{ text: "Hello" },
function (response) {
console.log("Received " + response)
}
)

In this blog post, 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.

内存/代码/剪贴板中的敏感信息

如果浏览器扩展将敏感信息存储在其内存中,这些信息可能会被转储(尤其是在 Windows 机器上)并被搜索到。

因此,不应将浏览器扩展的内存视为安全,不应存储诸如凭据或助记词等敏感信息

当然,不要把敏感信息放在代码中,因为代码会是公开的

要从浏览器转储内存,你可以转储进程内存,或者在浏览器扩展的设置中点击 Inspect pop-up -> 在 Memory 部分 -> Take a snaphost 并使用 CTRL+F 在快照中搜索敏感信息。

此外,像助记词或密码这样高度敏感的信息不应被允许复制到剪贴板(或者至少在几秒后从剪贴板中移除),因为那样监控剪贴板的进程就能获取到它们。

在浏览器中加载扩展

  1. Download the Browser Extension & unzipped
  2. 转到 chrome://extensions/启用 Developer Mode
  3. 点击 Load unpacked 按钮

Firefox 中,转到 about:debugging#/runtime/this-firefox 并点击 Load Temporary Add-on 按钮。

从商店获取源码

Chrome 扩展的源代码可以通过多种方法获得。下面对每种选项提供了详细的说明和步骤。

通过命令行将扩展下载为 ZIP

Chrome 扩展的源代码可以使用命令行下载为 ZIP 文件。这涉及使用 curl 从特定 URL 获取 ZIP 文件,然后将 ZIP 文件的内容解压到某个目录。步骤如下:

  1. 将 “extension_id” 替换为扩展的实际 ID。
  2. 执行以下命令:
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"

使用 CRX Viewer 网站

https://robwu.nl/crxviewer/

使用 CRX Viewer 扩展

另一种方便的方法是使用 Chrome Extension Source Viewer,这是一个开源项目。它可以从 Chrome Web Store 安装。查看器的源代码可在其 GitHub repository 获取。

查看本地已安装扩展的源代码

本地安装的 Chrome 扩展也可以被检查。方法如下:

  1. 访问 chrome://version/ 并定位 “Profile Path” 字段以进入你的 Chrome 本地配置文件目录。
  2. 在配置文件目录中导航到 Extensions/ 子文件夹。
  3. 该文件夹包含所有已安装的扩展,通常以可读格式包含其源代码。

要识别扩展,你可以将它们的 ID 映射到名称:

  • about:extensions 页面启用 Developer Mode 以查看每个扩展的 ID。
  • 在每个扩展的文件夹中,manifest.json 文件包含可读的 name 字段,可帮助你识别该扩展。

使用文件归档工具或解包器

前往 Chrome Web Store 并下载扩展。该文件将具有 .crx 扩展名。将文件扩展名从 .crx 更改为 .zip。使用任何文件归档工具(例如 WinRAR、7-Zip 等)解压该 ZIP 文件的内容。

在 Chrome 中使用开发者模式

打开 Chrome 并访问 chrome://extensions/。在右上角启用 “Developer mode”。点击 “Load unpacked extension…”。导航到你的扩展所在目录。此操作不会下载源代码,但对于查看和修改已下载或开发中的扩展代码非常有用。

Chrome extension manifest 数据集

为了尝试发现易受攻击的浏览器扩展,你可以使用该https://github.com/palant/chrome-extension-manifests-dataset 并检查它们的 manifest 文件以寻找潜在的漏洞迹象。例如,要检查具有超过 25000 名用户、包含 content_scripts 且具有权限 nativeMessaing 的扩展:

# 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')"

Post-exploitation: Forced extension load & persistence (Windows)

一种隐蔽技术,通过直接编辑 per-user Preferences 并伪造有效的 HMACs 来后门化 Chromium,导致浏览器在无提示或标志的情况下接受并激活任意 unpacked extension。

Forced Extension Load Preferences Mac Forgery Windows

Detecting Malicious Extension Updates (Static Version Diffing)

Supply-chain 攻击常常以对先前无害扩展的 恶意更新 形式出现。一种实用且噪声低的方法是使用静态分析(例如 Assemblyline)将 新扩展包与最后一个已知良好版本进行比较。目标是对 高信噪比的差异 发出告警,而不是对任何变更都报警。

Workflow

  • 提交两个版本(旧 + 新)到相同的静态分析配置。
  • 标记新增或更新的 background/service worker 脚本(持久化 + 特权逻辑)。
  • 标记新增或更新的 content scripts(DOM 访问与数据采集)。
  • 标记 manifest.json 中新增的 permissions/host_permissions
  • 标记从代码中提取到的新域名(潜在的 C2/外泄端点)。
  • 标记新的静态分析检测项(例如 base64 解码、cookie 收集、网络请求构建器、混淆模式)。
  • 标记统计学异常,比如更改脚本中熵的急剧上升或异常 z-score。

Detecting script changes accurately

  • 新增脚本 → 通过 manifest.json diff 检测。
  • 已有脚本被修改(manifest 未变)→ 比较从解压出的文件树得到的 per-file hashes(例如 Assemblyline Extract 输出)。这样可以捕获对已有 worker 或 content script 的隐蔽更新。

Pre-disclosure detections

为了避免基于已知 IOC 的“简单模式”检测,禁用基于 threat-intel 的服务,转而依赖内在信号(域名、启发式签名、脚本差异、熵异常)。这可以提高在公开报告前发现恶意更新的概率。

Example high-confidence alert logic

  • 低噪声组合: 新域名 + 新的静态分析检测 + 更新的 background/service worker + 更新或新增的 content scripts。
  • 更广覆盖: 新域名 + 新增或更新的 background/service worker(召回更高,但噪声也更高)。

Key Assemblyline services for this workflow:

  • Extract:解包扩展并生成每个文件的哈希。
  • Characterize:计算文件特征(例如熵)。
  • JsJAWS / FrankenStrings / URLCreator:提取 JS 启发式信息、字符串和域名以便版本间 diff。

Security Audit Checklist

尽管 Browser Extensions 的攻击面相对 有限,其中一些仍可能包含 漏洞需要强化的点。以下是最常见的项:

  • 尽可能限制请求的 permissions
  • 尽可能限制 host_permissions
  • 使用强健的 content_security_policy
  • 尽可能限制 externally_connectable,如果不需要且可行,不要默认放开,指定为 {}
  • 如果提到了对 XSS 或 takeover 易受攻击的 URL,攻击者将能够直接向 background scripts 发送消息。这是一个强力绕过手段。
  • 尽可能限制 web_accessible_resources,如果可能,甚至置空。
  • 如果 web_accessible_resources 非空,检查 ClickJacking
  • 如果任何 communicationextensionweb page 发生,检查通信中是否因交互而引入的 XSS 漏洞
  • 如果使用 Post Messages,检查 Post Message vulnerabilities
  • 如果 Content Script 访问 DOM 细节,检查当这些内容被 web 修改时是否引入 XSS
  • 特别关注如果该通信还涉及 Content Script -> Background script communication 的情况。
  • 如果 background script 通过 native messaging 进行通信,检查通信是否安全并进行了清洗。
  • 敏感信息不应被存储 在 Browser Extension 的 代码中
  • 敏感信息不应被存储 在 Browser Extension 的 内存中
  • 敏感信息不应被存储未受保护的文件系统中

Browser Extension Risks

  • 应用 https://crxaminer.tech/ 分析扩展请求的 permissions 等数据,以给出使用该扩展的风险等级。

Tools

Tarnish

  • 从提供的 Chrome webstore 链接拉取任何 Chrome extension。
  • manifest.json 查看器:以美化的 JSON 形式显示扩展的 manifest。
  • Fingerprint Analysis:检测 web_accessible_resources 并自动生成 Chrome extension 指纹化 JavaScript。
  • Potential Clickjacking Analysis:检测使用了 web_accessible_resources 指令的扩展 HTML 页面。根据页面用途,这些页面可能存在 clickjacking 风险。
  • Permission Warning(s) viewer:显示用户尝试安装扩展时会看到的 Chrome 权限提示列表。
  • Dangerous Function(s):显示可能被攻击者利用的危险函数位置(例如 innerHTML、chrome.tabs.executeScript 等)。
  • Entry Point(s):显示扩展接收用户/外部输入的位置。这有助于理解扩展的攻击面并寻找向扩展发送恶意构造数据的潜在点。
  • Dangerous Function(s) 和 Entry Point(s) 扫描器为其生成的告警提供:
    • 导致告警的相关代码片段和行号。
    • 问题描述。
    • “View File” 按钮以查看包含该代码的完整源文件。
    • 被告警文件的路径。
    • 被告警文件的完整 Chrome extension URI。
    • 文件类型,例如 Background Page 脚本、Content Script、Browser Action 等。
    • 如果易受攻击的行在 JavaScript 文件中,显示所有包含该文件的页面路径以及这些页面的类型和 web_accessible_resource 状态。
  • Content Security Policy (CSP) analyzer and bypass checker:指出扩展 CSP 的弱点,并展示由于白名单 CDN 等导致的潜在绕过方式。
  • Known Vulnerable Libraries:使用 Retire.js 检查是否使用已知有漏洞的 JavaScript 库。
  • 下载扩展及格式化版本。
  • 下载原始扩展。
  • 下载美化后的扩展版本(自动美化的 HTML 和 JavaScript)。
  • 自动缓存扫描结果:首次运行扩展扫描可能耗时较长,但如果扩展未更新,第二次运行几乎即时,因为结果被缓存。
  • 可链接的报告 URL,方便将生成的扩展报告分享给他人。

Neto

Project Neto 是一个面向 Python 3 的包,旨在分析并揭示 Firefox 和 Chrome 等知名浏览器插件与扩展的隐藏特性。它自动化解压打包文件的过程,从扩展中的相关资源(如 manifest.json、本地化文件夹或 Javascript/HTML 源文件)提取这些特性。

References

  • Thanks to @naivenom for the help with this methodology
  • 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/24/attack-surface-of-extension-pages/
  • https://palant.info/2022/08/31/when-extension-pages-are-web-accessible/
  • 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/mv2/background-pages
  • https://thehackerblog.com/kicking-the-rims-a-guide-for-securely-writing-and-auditing-chrome-extensions/
  • https://gist.github.com/LongJohnCoder/9ddf5735df3a4f2e9559665fb864eac0
  • https://redcanary.com/blog/threat-detection/assemblyline-browser-extensions/

Tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks