.. | ||
browext-clickjacking.md | ||
browext-permissions-and-host_permissions.md | ||
browext-xss-example.md | ||
README.md |
浏览器扩展渗透测试方法论
从零到英雄学习AWS黑客攻击 htARTE (HackTricks AWS Red Team Expert)!
支持HackTricks的其他方式:
- 如果您想在HackTricks中看到您的公司广告或下载HackTricks的PDF,请查看订阅计划!
- 获取官方PEASS & HackTricks商品
- 发现PEASS家族,我们独家的NFTs系列
- 加入 💬 Discord群组 或 telegram群组 或在Twitter 🐦 上关注我 @carlospolopm。
- 通过向 HackTricks 和 HackTricks Cloud github仓库提交PR来分享您的黑客技巧。
基本信息
浏览器扩展使用JavaScript编写,并由浏览器在后台加载。它有自己的DOM,但可以与其他网站的DOM交互。这意味着它可能会危害其他网站的机密性、完整性和可用性(CIA)。
主要组件
扩展布局在可视化时看起来最佳,由三个组件组成。让我们深入了解每个组件。
内容脚本
每个内容脚本都可以直接访问单个网页的DOM,因此暴露于潜在的恶意输入。然而,内容脚本除了能够向扩展核心发送消息之外,没有其他权限。
扩展核心
扩展核心包含了大部分扩展权限/访问权,但扩展核心只能通过XMLHttpRequest和内容脚本与网页内容交互。此外,扩展核心无法直接访问宿主机器。
本地二进制
扩展允许一个本地二进制文件,该文件可以**以用户的全部权限访问宿主机器。**本地二进制文件通过标准的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_globs
和 exclude_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 Developers文档中找到列表,对于Firefox,MDN是最终的信息来源。
记住内容脚本也可以与后台脚本通信,以便它们执行操作并发送回响应
{% endhint %}
要在Chrome中查看和调试内容脚本,你可以从选项 > 更多工具 > 开发者工具中打开Chrome开发者工具菜单,或者(按 - Ctrl + Shift + I)。
在开发者工具显示后,点击源代码标签页,然后点击内容脚本标签页。在这里你可以看到各种扩展的运行中的内容脚本,并设置断点以监控执行流程。
注入的内容脚本
{% 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"
}
}
- 点击时注入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://*.nytimes.com/*" ],
excludeMatches : [ "*://*/*business*" ],
js : [ "contentScript.js" ],
}]);
// ANother example
chrome.tabs.executeScript(tabId, { file: "content_script.js" });
为了包含或排除更多的URL,也可以使用 include_globs
和 exclude_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://*.nytimes.com/*"],
"run_at": "document_idle",
"js": ["contentScript.js"]
}
],
...
}
通过 service-worker.js
chrome.scripting.registerContentScripts([{
id : "test",
matches : [ "https://*.nytimes.com/*" ],
runAt : "document_idle",
js : [ "contentScript.js" ],
}]);
背景
当内容脚本发送消息时,其目的地是背景页面。背景页面是一个特殊的页面,除非在扩展清单中另有指定,否则它始终存在。尽管它是一个拥有自己的DOM等的普通页面,用户却看不见它。它的功能通常是协调扩展的所有其他部分。
{% hint style="success" %} 如果没有明确声明背景页面,浏览器会很有帮助地自动生成一个,并确保所有声明的背景脚本都加载进去,就像前面的manifest.json示例中那样。 {% 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 在新标签页中打开页面。
要调试背景脚本,您可以转到 扩展详情并检查服务工作器, 这将打开带有背景脚本的开发者工具:
选项页面和其他
浏览器扩展可以包含各种页面:
- 动作页面 在点击扩展图标时显示在 下拉菜单中。
- 扩展将在新标签页中 加载的页面。
- 选项页面:点击扩展时,此页面显示在扩展顶部。在之前的 manifest 中,在我的案例中,我可以通过
chrome://extensions/?options=fadlhnelkbeojnebcbkacjilhnbjfjca
访问此页面或点击:
与背景页面不同,这些页面不是持久的,而是在需要时加载。然而,所有这些页面都可以 接收来自内容脚本的消息。并且它们都有 完全访问特定于扩展的 API 的权限,只要扩展的权限允许。
总的来说,浏览器扩展的相关上下文如下所示:
permissions
和 host_permissions
permissions
和 host_permissions
是 manifest.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
在公共扩展中,extension-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.connect 和 runtime.sendMessage 连接到你的扩展。
- 如果
externally_connectable
键在你的扩展清单中 没有声明,或者声明为"ids": ["*"]
,所有扩展都可以连接,但没有网页可以连接。 - 如果声明了 特定的 ID,如
"ids": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
,只有那些应用程序可以连接。 - 如果指定了 匹配项,那些网页应用将能够连接:
"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
- 如果指定为空:
"externally_connectable": {}
,则没有任何应用程序或网页能够连接。
这里指示的扩展和URL越少,攻击面就越小。
{% hint style="danger" %}
如果在 externally_connectable
中指定了易受XSS或接管攻击的网页,攻击者将能够直接向后台脚本发送消息,完全绕过内容脚本及其CSP。
因此,这是一个非常强大的绕过。 {% endhint %}
网页 ↔︎ 内容脚本通信
尽管内容脚本和托管它们的页面的执行环境是相互隔离的,但它们共享对页面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- 内容脚本可能只在用户执行某些操作时才期望接收消息
- 源域名:可以与允许域名列表进行比对。
- 如果使用正则表达式,要非常小心
- 来源:
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
这不是“确切的”通信方式,但是网页和内容脚本将能够访问网页 DOM。因此,如果内容脚本从中读取一些信息,信任网页 DOM,网页可能会修改这些数据(因为网页不应该被信任,或者因为网页容易受到 XSS 的攻击)并且危及内容脚本。
您还可以在以下示例中找到一个基于 DOM 的 XSS 来危及浏览器扩展的例子:
{% content-ref url="browext-xss-example.md" %} browext-xss-example.md {% endcontent-ref %}
内存/代码中的敏感信息
如果浏览器扩展在其内存中存储了敏感信息,这些信息可能会被转储(特别是在 Windows 机器上)并且搜索这些信息。
因此,浏览器扩展的内存不应该被认为是安全的,而且像凭据或助记词这样的敏感信息也不应该存储。
当然,也不要将敏感信息放在代码中,因为它会是公开的。
内容脚本 ↔︎ 背景脚本通信
内容脚本可以使用函数 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);
})();
将请求从扩展(通常是背景脚本)发送到内容脚本类似,但您需要指定发送到哪个标签页。此示例演示了将消息发送到选定标签页中的内容脚本。
(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 事件监听器来处理消息。无论是在内容脚本还是扩展页面中,这看起来都是相同的。
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()
被同步调用。如果你想要异步使用 sendResponse()
,在 onMessage
事件处理器中添加 return true;
。
如果多个页面在监听
onMessage
事件,只有第一个调用sendResponse()
的对于特定事件将成功发送响应。对那个事件的所有其他响应将被忽略。
对于新的扩展,你应该优先使用 promises 而不是 callbacks。如果你在使用 callbacks,sendResponse()
回调只有在同步使用,或者事件处理器返回 true
来表明它将异步响应时才有效。如果没有处理器返回 true 或者 sendResponse()
回调被垃圾回收,sendMessage()
函数的回调将会自动被调用。
在浏览器中加载扩展
- 下载 浏览器扩展并解压
- 转到
chrome://extensions/
并启用开发者模式
- 点击
加载已解压的扩展程序
按钮
在 Firefox 中,你转到 about:debugging#/runtime/this-firefox
并点击 加载临时附加组件
按钮。
从商店获取源代码
从 这里:
选项 1: 命令行下载扩展为 zip 并解压
{% code overflow="wrap" %}
extension_id=jifpbeccnghkjeaalbbjmodiffmgedin # change this 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"
{% endcode %}
感谢crxviewer提供的神奇下载URL。
选项2:使用CRX Viewer网站
选项3:使用CRX Viewer扩展程序
Chrome扩展程序源代码查看器是开源的(github仓库),使这一切变得非常简单。
选项3:查看本地安装的扩展程序源代码
- 找到您的Chrome本地配置文件目录。打开
chrome://version/
并找到"Profile Path:"字段。打开那个文件夹。 - 打开
Extensions/
子文件夹 - 这里有所有的扩展程序,通常源代码是可读的。
本地安装的扩展程序ID与名称之间的映射
- 在
about:extensions
上,打开开发者模式,您将在每个条目下看到ID - 在
Extensions/
文件夹内,manifest.json有一个可读的name
字段
安全审计清单
尽管浏览器扩展程序的攻击面有限,但其中一些可能包含漏洞或潜在的加固改进。以下是最常见的一些:
- 尽可能限制请求的**
permissions
** - 尽可能限制
host_permissions
- 使用强大的
content_security_policy
- 尽可能限制
externally_connectable
,如果不需要且可能的话,不要默认留下,指定**{}
** - 如果这里提到了容易受到XSS或接管攻击的URL,攻击者将能够直接向后台脚本发送消息。非常强大的绕过。
- 尽可能限制
web_accessible_resources
,如果可能的话,甚至为空。 - 如果
web_accessible_resources
不是空的,检查ClickJacking - 如果扩展程序与网页之间发生任何通信,检查XSS 漏洞是否因通信而引起。
- 如果使用了Post Messages,检查Post Message漏洞
- 如果内容脚本访问DOM细节,检查它们是否引入了XSS,如果它们被网页修改
- 如果这种通信也涉及到内容脚本 -> 后台脚本通信,特别强调
- 敏感信息不应该存储在浏览器扩展程序代码中
- 敏感信息不应该存储在浏览器扩展程序内存中
工具
Tarnish
- 从提供的Chrome网上应用店链接中提取任何Chrome扩展程序。
- manifest.json 查看器:简单地显示扩展程序清单的JSON美化版本。
- 指纹分析:检测web_accessible_resources并自动生成Chrome扩展程序指纹识别JavaScript。
- 潜在的Clickjacking分析:检测设置了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
Neto项目是一个Python 3包,旨在分析和揭示Firefox和Chrome等知名浏览器的浏览器插件和扩展程序的隐藏功能。它自动化了解压缩包文件的过程,以从扩展程序中的相关资源如manifest.json
、本地化文件夹或Javascript和HTML源文件中提取这些功能。
参考资料
- 感谢 @naivenom 在这个方法论上的帮助
- 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/
通过 htARTE (HackTricks AWS Red Team Expert)从零开始学习AWS黑客攻击!
支持HackTricks的其他方式:
- 如果您想在HackTricks中看到您的公司广告或以PDF格式下载HackTricks,请查看订阅计划!
- 获取官方PEASS & HackTricks商品
- 发现PEASS家族,我们独家的NFTs收藏
- 加入 💬 Discord群组 或 telegram群组 或在Twitter 🐦 上关注我 @carlospolopm。
- 通过向 HackTricks 和 HackTricks Cloud github仓库提交PR来分享您的黑客技巧。