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

根据浏览器的不同,内容脚本的功能可能略有不同。对于基于Chromium的浏览器,功能列表可在Chrome开发者文档中找到,而对于Firefox,MDN是主要来源。 值得注意的是,内容脚本具有与后台脚本通信的能力,使其能够执行操作并传递响应。

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

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

注入的内容脚本

请注意内容脚本不是强制性的,因为也可以通过**tabs.executeScript在网页中动态注入脚本并以编程方式注入它们。这实际上提供了更多细粒度的控制**。

要对内容脚本进行编程注入,扩展需要具有主机权限,以便将脚本注入到页面中。这些权限可以通过在扩展的清单中请求它们或通过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" });

Content Scripts 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中的条目,将指示浏览器扩展具有哪些权限(存储、位置...)和在哪些网页中。

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

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

pageBrowExt - permissions & host_permissions

content_security_policy

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

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

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

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

pageContent 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可能是动态的**。

允许访问这些页面使这些页面潜在易受 ClickJacking 攻击

pageBrowExt - ClickJacking

只允许扩展加载这些页面,而不是随机URL,可以防止 ClickJacking 攻击。

externally_connectable

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

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

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

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

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

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

如果一个容易受到 XSS 或接管攻击的网页在**externally_connectable**中被指定,攻击者将能够直接向后台脚本发送消息,完全绕过内容脚本及其 CSP。

因此,这是一个非常强大的绕过方式

此外,如果客户端安装了一个恶意扩展,即使它没有被允许与易受攻击的扩展通信,它也可以在允许的网页中注入XSS 数据,或者滥用**WebRequestDeclarativeNetRequest** API来操纵针对特定域的请求,改变页面对JavaScript 文件的请求(请注意,目标页面上的 CSP 可能会阻止这些攻击)。这个想法来自这篇文章

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绕过

pagePostMessage Vulnerabilities

Iframe

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

pageBrowExt - XSS Example

DOM

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

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

pageBrowExt - 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 本地配置文件目录,方法是访问 chrome://version/ 并找到“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 中的弱点,并且会阐明绕过 CSP 的潜在方法,例如白名单 CDN 等。

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

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

  • 下载原始扩展。

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

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

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

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

参考资料

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

支持 HackTricks 的其他方式:

最后更新于