Browser Extension Pentesting Methodology

从零开始学习AWS黑客技术,成为专家 htARTE(HackTricks AWS Red Team Expert)

支持HackTricks的其他方式:

基本信息

浏览器扩展是用JavaScript编写的,并由浏览器在后台加载。它有自己的DOM,但可以与其他网站的DOM进行交互。这意味着它可能会危及其他网站的机密性、完整性和可用性(CIA)。

主要组件

扩展布局在可视化时效果最佳,由三个组件组成。让我们深入了解每个组件。

内容脚本

每个内容脚本直接访问单个网页的DOM,因此容易受到潜在恶意输入的影响。但是,内容脚本除了能够向扩展核心发送消息外,不具备其他权限。

扩展核心

扩展核心包含大部分扩展的权限/访问,但扩展核心只能通过XMLHttpRequest和内容脚本与Web内容进行交互。此外,扩展核心无法直接访问主机机器。

本地二进制

扩展允许本地二进制文件以用户完整权限访问主机机器。本地二进制通过标准的Netscape插件应用程序编程接口(NPAPI)与扩展核心进行交互,该接口被Flash和其他浏览器插件使用。

边界

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

此外,内容脚本通过在单独的JavaScript堆中运行与其关联的网页分离。内容脚本和网页具有对同一底层DOM的访问权限,但两者不会交换JavaScript指针,从而防止JavaScript功能的泄漏。

manifest.json

Chrome扩展只是一个带有.crx文件扩展名的ZIP文件夹。扩展的核心是位于文件夹根目录下的**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

内容脚本在用户导航到匹配页面时加载,对于我们的情况是任何匹配 https://example.com/* 表达式的页面,但不匹配 *://*/*/business* 正则表达式。它们像页面自己的脚本一样执行,并且可以任意访问页面的文档对象模型(DOM)

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

为了包含或排除更多的URL,也可以使用**include_globsexclude_globs**。

以下是一个示例内容脚本,当使用存储API从扩展的存储中检索message值时,将在页面上添加一个解释按钮。

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是少数例外之一。对于超出这些例外的功能,消息将发送到内容脚本可以与之通信的扩展页面。

要在Chrome中查看和调试内容脚本,可以从“选项”>“更多工具”>“开发者工具”或按Ctrl + Shift + I打开Chrome开发者工具菜单。

在显示开发者工具后,应单击源标签,然后单击内容脚本标签。这样可以观察各种扩展中正在运行的内容脚本,并设置断点以跟踪执行流程。

注入的内容脚本

要对内容脚本进行编程注入,扩展需要具有主机权限,以便将脚本注入到页面中。这些权限可以通过在扩展的清单中请求它们或通过activeTab在临时基础上获得。

基于activeTab的示例扩展

manifest.json
{
"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" });

为了包含或排除更多的URL,也可以使用**include_globsexclude_globs**。

内容脚本 run_at

run_at字段控制何时将JavaScript文件注入到网页中。首选和默认值是"document_idle"

可能的值包括:

  • document_idle:尽可能早

  • document_start:在css文件之后,但在构建任何其他DOM或运行任何其他脚本之前。

  • document_end:在DOM完成后立即,但在加载图像和框架等子资源之前。

通过 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" ],
}]);

背景

内容脚本发送的消息由背景页接收,背景页在协调扩展组件方面起着中心作用。值得注意的是,背景页在整个扩展的生命周期中持续存在,不需要直接用户交互。它拥有自己的文档对象模型(DOM),可以实现复杂的交互和状态管理。

关键要点

  • 背景页角色: 充当扩展的神经中枢,确保扩展各部分之间的通信和协调。

  • 持久性: 它是一个始终存在的实体,对用户不可见但对扩展的功能至关重要。

  • 自动生成: 如果没有明确定义,浏览器将自动创建一个背景页。这个自动生成的页面将包括扩展清单中指定的所有背景脚本,确保扩展的后台任务无缝运行。

示例背景脚本:

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

它使用runtime.onMessage API来监听消息。当收到一个"explain"消息时,它使用tabs API在新标签页中打开一个页面。

要调试后台脚本,您可以转到扩展详细信息并检查服务工作者,这将使用后台脚本打开开发者工具:

选项页面和其他

浏览器扩展可以包含各种类型的页面:

  • 操作页面在单击扩展图标时显示在下拉菜单中。

  • 扩展将在新标签页中加载的页面。

  • 选项页面:单击时显示在扩展顶部的页面。在我的情况下,我能够在chrome://extensions/?options=fadlhnelkbeojnebcbkacjilhnbjfjca或单击以下链接中访问此页面:

请注意,这些页面不像后台页面那样持久,因为它们根据需要动态加载内容。尽管如此,它们与后台页面共享某些功能:

  • **与内容脚本通信:**与后台页面类似,这些页面可以从内容脚本接收消息,促进扩展内的交互。

  • **访问扩展特定的API:**这些页面可以全面访问扩展特定的API,取决于为扩展定义的权限。

permissionshost_permissions

permissionshost_permissionsmanifest.json中的条目,将指示浏览器扩展具有哪些权限(存储、位置...)和在哪些网页中。

由于浏览器扩展可能具有如此特权,一个恶意的扩展或被入侵的扩展可能允许攻击者以不同方式窃取敏感信息并监视用户

查看这些设置如何工作以及它们如何可能在以下情况中被滥用:

BrowExt - permissions & host_permissions

content_security_policy

内容安全策略也可以在manifest.json中声明。如果定义了一个,它可能会存在漏洞

浏览器扩展页面的默认设置相当严格:

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

有关CSP和潜在的绕过方法的更多信息,请查看:

Content Security Policy (CSP) Bypass

web_accessible_resources

为了让网页访问浏览器扩展的页面,比如一个.html页面,这个页面需要在manifest.json的**web_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

在公共扩展中,扩展ID是可访问的

尽管如此,如果使用manifest.json参数**use_dynamic_url,则此ID可以是动态的**。

允许访问这些页面使这些页面潜在易受点击劫持攻击

BrowExt - ClickJacking

externally_connectable

根据文档"externally_connectable"清单属性声明了哪些扩展和网页可以通过runtime.connectruntime.sendMessage连接到您的扩展

  • 如果在您的扩展清单中声明**externally_connectable键,或者声明为"ids": ["*"]**,所有扩展都可以连接,但没有网页可以连接

  • 如果指定了特定的ID,例如"ids": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]只有这些应用程序可以连接。

  • 如果指定了匹配项,这些Web应用程序将能够连接:

"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
  • 如果指定为空:"externally_connectable": {},则没有应用程序或网页能够连接。

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

Web ↔︎ 内容脚本通信

内容脚本运行的环境和主机页面存在的环境是分离的,确保了隔离。尽管存在这种隔离,但两者都可以与页面的文档对象模型(DOM)进行交互,这是一个共享资源。为了使主机页面与内容脚本进行通信,或者间接地通过内容脚本与扩展进行通信,需要利用双方都可以访问的DOM作为通信渠道。

发送消息

content-script.js
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);
port.postMessage(event.data.text);
}
}, false);
example.js
document.getElementById("theButton").addEventListener("click", () => {
window.postMessage(
{type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);

安全的Post Message通信应该检查接收到的消息的真实性,可以通过以下方式进行检查:

  • event.isTrusted:只有在事件由用户操作触发时才为True

  • 内容脚本可能只期望在用户执行某些操作时接收消息

  • 来源域:可能只允许白名单中的域发送消息

  • 如果使用正则表达式,请非常小心

  • 来源received_message.source !== window可用于检查消息是否来自内容脚本正在侦听的同一窗口

即使执行了上述检查,也可能存在漏洞,请在以下页面检查潜在的Post Message绕过

PostMessage Vulnerabilities

Iframe

另一种可能的通信方式可能是通过Iframe URLs,您可以在以下示例中找到:

BrowExt - XSS Example

DOM

这并不是“确切地”一种通信方式,但是web和内容脚本将可以访问web DOM。因此,如果内容脚本从中读取一些信息,信任web DOM,web可能会修改这些数据(因为不应信任web,或者因为web容易受到XSS攻击),并危及内容脚本

您还可以在以下示例中找到一个基于DOM的XSS示例来危及浏览器扩展

BrowExt - XSS Example

内存/代码中的敏感信息

如果浏览器扩展在其内存中存储敏感信息,这些信息可能会被转储(特别是在Windows机器上),并且可以对这些信息进行搜索

因此,浏览器扩展的内存不应被视为安全敏感信息(如凭据或助记词短语)不应存储

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

要从浏览器中转储内存,您可以转储进程内存或转到浏览器扩展的设置,单击**检查弹出窗口** -> 在**内存部分 -> 拍摄快照,然后使用CTRL+F**在快照中搜索敏感信息。

内容脚本 ↔︎ 后台脚本通信

内容脚本可以使用函数runtime.sendMessage() tabs.sendMessage() 发送一次性可JSON序列化消息。

要处理响应,请使用返回的Promise。尽管出于向后兼容性考虑,仍然可以将回调函数作为最后一个参数传递。

内容脚本发送请求如下所示:

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

扩展程序(通常是后台脚本)发送请求。内容脚本可以使用这些函数,只是需要指定要发送到哪个标签页。以下是向所选标签页中的内容脚本发送消息的示例:

// 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 事件侦听器来处理消息。无论是来自内容脚本还是扩展页面,这看起来都是一样的。

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

在突出显示的示例中,sendResponse() 以同步方式执行。要修改onMessage事件处理程序以异步执行sendResponse(),必须加入return true;

一个重要的考虑因素是,在设置多个页面接收onMessage事件的情况下,第一个执行sendResponse() 的页面将是唯一能够有效传递响应的页面。对于同一事件的任何后续响应将不被考虑。

在编写新扩展时,应优先选择使用 promises 而不是回调。关于回调的使用,只有在直接在同步上下文中执行sendResponse(),或者如果事件处理程序通过返回true指示异步操作时,sendResponse()函数才被视为有效。如果没有处理程序返回true,或者如果sendResponse()函数从内存中删除(被垃圾回收),则与sendMessage()函数关联的回调将默认触发。

在浏览器中加载扩展

  1. 下载浏览器扩展并解压缩

  2. 转到**chrome://extensions/启用**开发者模式

  3. 点击**加载已解压的扩展程序**按钮

Firefox中,转到**about:debugging#/runtime/this-firefox,然后点击加载临时附加组件**按钮。

从商店获取源代码

Chrome扩展的源代码可以通过各种方法获取。以下是每种选项的详细说明和说明。

通过命令行下载ZIP格式的扩展

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

  1. 用实际的扩展ID替换"extension_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 扩展源代码查看器,这是一个开源项目。可以从Chrome 网上应用店安装。查看器的源代码可在其GitHub 存储库中找到。

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

也可以检查本地安装的 Chrome 扩展。操作步骤如下:

  1. 访问 chrome://version/ 查看 Chrome 本地配置文件目录,并找到“Profile Path”字段。

  2. 转到配置文件目录中的 Extensions/ 子文件夹。

  3. 该文件夹包含所有已安装的扩展,通常以可读格式显示其源代码。

要识别扩展,可以将其 ID 映射到名称:

  • about:extensions 页面上启用开发者模式,查看每个扩展的 ID。

  • 在每个扩展的文件夹中,manifest.json 文件包含一个可读的 name 字段,可帮助您识别扩展。

使用文件压缩工具或解包工具

前往 Chrome 网上应用店并下载扩展。文件将具有 .crx 扩展名。将文件扩展名从 .crx 更改为 .zip。使用任何文件压缩工具(如 WinRAR、7-Zip 等)提取 ZIP 文件的内容。

在 Chrome 中使用开发者模式

打开 Chrome 并转到 chrome://extensions/。在右上角启用“开发者模式”。单击“加载已解压的扩展...”。导航到扩展的目录。这不会下载源代码,但对于查看和修改已下载或已开发的扩展的代码很有用。

安全审计清单

尽管浏览器扩展具有有限的攻击面,但其中一些可能包含漏洞潜在的加固改进。以下是最常见的几种:

工具

  • 从提供的 Chrome 网上应用商店链接中提取任何 Chrome 扩展。

  • manifest.json 查看器:简单显示扩展清单的 JSON 格式化版本。

  • 指纹分析:检测web_accessible_resources并自动生成 Chrome 扩展指纹识别 JavaScript。

  • 潜在点击劫持分析:检测具有web_accessible_resources指令设置的扩展 HTML 页面。根据页面用途,这些页面可能容易受到点击劫持攻击。

  • 权限警告查看器:显示用户尝试安装扩展时将显示的所有 Chrome 权限提示警告列表。

  • 危险函数:显示可能被攻击者利用的危险函数位置(例如 innerHTML、chrome.tabs.executeScript 等函数)。

  • 入口点:显示扩展接受用户/外部输入的位置。这对于了解扩展的表面积并寻找潜在的发送恶意数据到扩展的点很有用。

  • 危险函数和入口点扫描器生成的警报包括以下内容:

  • 引发警报的相关代码片段和行。

  • 问题描述。

  • “查看文件”按钮,可查看包含代码的完整源文件。

  • 警报文件的路径。

  • 警报文件的完整 Chrome 扩展 URI。

  • 文件类型,如后台页面脚本、内容脚本、浏览器操作等。

  • 如果易受攻击的行在 JavaScript 文件中,则包括其所包含的所有页面的路径以及这些页面的类型,以及web_accessible_resource状态。

  • 内容安全策略 (CSP) 分析器和绕过检查器:指出扩展 CSP 中的弱点,并阐明由于白名单 CDN 等原因绕过 CSP 的潜在方法。

  • 已知易受攻击的库:使用Retire.js检查已知易受攻击的 JavaScript 库的使用情况。

  • 下载扩展和格式化版本。

  • 下载原始扩展。

  • 下载扩展的美化版本(自动美化的 HTML 和 JavaScript)。

  • 自动缓存扫描结果,第一次运行扩展扫描将花费大量时间。但第二次,假设扩展未更新,由于结果已缓存,几乎会立即完成。

  • 可链接的报告 URL,轻松地将其他人链接到 tarnish 生成的扩展报告。

Neto 项目是一个 Python 3 包,旨在分析和解开浏览器插件和扩展的隐藏功能,适用于 Firefox 和 Chrome 等知名浏览器。它自动解压打包文件,从扩展中提取这些功能,如 manifest.json、本地化文件夹或 JavaScript 和 HTML 源文件。

参考资料

从零开始学习 AWS 黑客技术,成为专家 htARTE(HackTricks AWS 红队专家)

支持 HackTricks 的其他方式:

最后更新于