hacktricks/pentesting-web/browser-extension-pentesting-methodology
2024-05-05 22:03:00 +00:00
..
browext-clickjacking.md Translated ['README.md', 'binary-exploitation/arbitrary-write-2-exec/aw2 2024-05-05 22:03:00 +00:00
browext-permissions-and-host_permissions.md Translated ['README.md', 'binary-exploitation/arbitrary-write-2-exec/aw2 2024-05-05 22:03:00 +00:00
browext-xss-example.md Translated ['mobile-pentesting/ios-pentesting/ios-protocol-handlers.md', 2024-02-09 08:09:21 +00:00
README.md Translated ['README.md', 'binary-exploitation/arbitrary-write-2-exec/aw2 2024-05-05 22:03:00 +00:00

浏览器扩展渗透测试方法论

从零开始学习AWS黑客技术成为专家 htARTEHackTricks AWS Red Team Expert

支持HackTricks的其他方式

基本信息

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

主要组件

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

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

内容脚本

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

扩展核心

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

本地二进制

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

边界

{% hint style="danger" %} 要获取用户的完整权限,攻击者必须说服扩展将内容脚本中的恶意输入传递给扩展核心,再从扩展核心传递给本地二进制。 {% endhint %}

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

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

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

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

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

注入的内容脚本

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

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

示例基于activeTab的扩展

{% code title="manifest.json" %}

{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}

{% endcode %}

  • 点击时注入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可以实现复杂的交互和状态管理。

关键要点

  • 背景页角色: 充当扩展的神经中枢,确保扩展各部分之间的通信和协调。
  • 持久性: 它是一个始终存在的实体,对用户不可见但对扩展功能至关重要。
  • 自动生成: 如果未明确定义,浏览器将自动创建一个背景页。这个自动生成的页面将包括扩展清单中指定的所有背景脚本,确保扩展的后台任务无缝运行。

{% hint style="success" %} 浏览器自动生成背景页(未明确定义时)提供的便利性确保了所有必要的背景脚本被集成和运行,简化了扩展的设置过程。 {% endhint %}

示例背景脚本:

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

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

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

{% content-ref url="browext-permissions-and-host_permissions.md" %} browext-permissions-and-host_permissions.md {% endcontent-ref %}

content_security_policy

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

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

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

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

{% content-ref url="../content-security-policy-csp-bypass/" %} content-security-policy-csp-bypass {% endcontent-ref %}

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 攻击

{% content-ref url="browext-clickjacking.md" %} browext-clickjacking.md {% endcontent-ref %}

{% hint style="success" %} 只允许扩展加载这些页面而不是随机URL可以防止 ClickJacking 攻击。 {% endhint %}

externally_connectable

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

  • 如果在您的扩展清单中声明**externally_connectable键,或者声明为"ids": ["*"]**所有扩展都可以连接,但没有网页可以连接
  • 如果指定了特定的ID,例如"ids": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]只有这些应用程序可以连接。
  • 如果指定了匹配项这些Web应用程序将能够连接
"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
  • 如果指定为空:"externally_connectable": {},则没有应用程序或网页能够连接。

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

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

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

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

Web ↔︎ 内容脚本通信

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

发送消息

{% code title="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);

{% endcode %}

{% code title="example.js" %}

document.getElementById("theButton").addEventListener("click", () => {
window.postMessage(
{type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);

{% endcode %}

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

  • event.isTrusted只有在事件由用户操作触发时才为True
  • 内容脚本可能只期望在用户执行某些操作时接收消息
  • origin域:可能只允许白名单域的消息
  • 如果使用正则表达式,请非常小心
  • 来源received_message.source !== window 可用于检查消息是否来自内容脚本正在监听的同一窗口

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

{% content-ref url="../postmessage-vulnerabilities/" %} postmessage-vulnerabilities {% endcontent-ref %}

Iframe

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

{% content-ref url="browext-xss-example.md" %} browext-xss-example.md {% endcontent-ref %}

DOM

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

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

{% content-ref url="browext-xss-example.md" %} browext-xss-example.md {% endcontent-ref %}

内存/代码中的敏感信息

如果浏览器扩展在其内存中存储敏感信息,这些信息可能会被转储特别是在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 Web商店安装。查看器的源代码可在其GitHub存储库中找到。

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

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

  1. 访问Chrome本地配置文件目录访问chrome://version/并找到“Profile Path”字段。
  2. 转到配置文件目录中的Extensions/子文件夹。
  3. 此文件夹包含所有已安装的扩展,通常以可读格式显示其源代码。

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

  • about:extensions页面上启用开发者模式以查看每个扩展的ID。
  • 在每个扩展的文件夹中,manifest.json文件包含一个可读的name字段,可帮助您识别扩展。

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

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

在Chrome中使用开发者模式

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

安全审计清单

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

  • 尽可能限制所请求的**权限**
  • 尽可能限制所请求的**host_permissions**
  • 使用强大的 content_security_policy
  • 尽可能限制externally_connectable,如果不需要且可能的话,不要默认留下,指定**{}**
  • 如果此处提到了易受XSS或接管攻击的URL,攻击者将能够直接向后台脚本发送消息。非常强大的绕过方式。
  • 尽可能限制web_accessible_resources,如果可能,甚至为空。
  • 如果**web_accessible_resources**不是空的,请检查点击劫持
  • 如果从扩展网页有任何通信,请检查由通信引起的XSS漏洞
  • 如果使用Post Messages请检查Post Message漏洞
  • 如果内容脚本访问DOM详细信息,请检查它们是否在被网页修改时引入了XSS
  • 特别强调如果此通信还涉及内容脚本 -> 后台脚本通信
  • 敏感信息不应存储在浏览器扩展代码
  • 敏感信息不应存储在浏览器扩展内存

工具

Tarnish

  • 从提供的Chrome Web商店链接中提取任何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

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

参考资料

从零开始学习AWS黑客技术成为专家 htARTEHackTricks AWS Red Team Expert

支持HackTricks的其他方式