结合漏洞案例,分析各组件的攻击面,最终绘制完整的攻击链路图
漏洞特点
攻击场景设计是在用户在访问恶意站点,触发漏洞利用
前端漏洞主要还是围绕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-eval和unsafe-inline,也无法添加任何远程地址,导致想要在Manifest V3实现XSS非常困难,不过好在现在大部分还在用Manifest V2(大概75%)

攻击面分析
从历史漏洞中学习,分析攻击面,总结攻击链路,为漏洞挖掘提供思路
但在Chrome扩展研究中存在一个比较大的问题:扩展没有官方提供历史版本,导致难以复现进行研究
但也有人收集历史数据,钱能解决问题
下面在此基础上,结合漏洞案例,介绍各组件存在的攻击面,以及相关攻击链路
Content scripts
Content scripts作为最贴近网页的扩展组件,大多数的攻击起点也都是在这里
主要攻击入口有DOM元素,事件,window.name,Web Storage
DOM
由于Content scripts与网页JS共享DOM
对于恶意站点来说,攻击入口包括了整个DOM
对于目标站点,攻击入口则是该域下所有可控的DOM,依赖站点特性,这里不多介绍
Scratch Addons (CVE-2020-26239)
DOM->Content->DOM
DOM元素的textContent写入outerHTML,导致XSS
1 | document.querySelectorAll(".project-description a").forEach((element) => { |
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 | const message = msgContainer.data("message"); |
攻击payload如下
1 | <div class="msg" |
https://palant.info/2022/03/14/party-time-injecting-code-into-teleparty-extension/
Video Downloader
DOM->Content->Background->Popup DOM
Content在DOM中寻找链接,传递给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 | document.addEventListener("connect", function (e) { |
在DOM中构造对应事件数据,最终利用WebEx相关DLL功能实现RCE
1 | document.dispatchEvent( |
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与网页使用同一个window,postMessage将直接与Content scripts通信
如果没有校验来源,甚至可以利用iframe或者window.open获取目标window,跨域发送消息直达Content scripts完成攻击
LastPass
Msg->Content->Background
未校验来源,直接把消息转发到Background scripts
1 | window.addEventListener("message", 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 | window.postMessage({ |
https://guard.io/blog/evernote-universal-xss-vulnerability
Adobe Acrobat
XSS->Msg->Content->Background
为了实现在documentcloud.adobe.com中查看PDF,会注入特定的Content scripts与Background scripts通信,利用Background scripts无同源策略,将PDF文件传输到documentcloud.adobe.com
documentcloud.adobe.com想查看https://internal/file.pdf
- 把链接通过postMessage发给
Content scripts Content scripts把链接发给Background scriptsBackground scripts无同源策略限制,fetch直接获取文件- 一步步回传给
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 | if (window.name && 0 == window.name.indexOf("pp_pending_ntf:")) { |
但是由于这是一个Manifest V3扩展,会被CSP拦截,不能实现实际的攻击
Web Storage
在Web中使用存储有
localStoragesessionStorageindexedDBWeb SQL
如果在Content中使用Web Storage,其实是在使用当前网站的存储,而不是扩展程序自己的
写入Web Storage存在信息泄露,同时读取Web Storage也是用户输入
有时使用Web Storage并不是扩展本身的意愿,而是扩展程序使用的依赖库在使用Web Storage
在Background中,由于有自己的页面,所以是存储在扩展中,不过在Chrome扩展中推荐使用的Chrome API提供的存储能力
chrome.storage.localchrome.storage.syncchrome.storage.sessionchrome.storage.managed
Skype
在页面中点击Skype扩展时,Content会将userId存储在sessionStorage中,恶意网站可在自己的sessionStorage读取
userId包含Skype ID,可以根据这个ID搜索到Skype账号
1 | setUserId: function(userId) |
https://palant.info/2022/03/01/skype-extension-all-functionality-broken-still-exploitable/
彩云小译
在启动翻译时,Content中的trs.js会写入指纹数据到localStorage
其中browserId和deviceId是扩展程序生成的指纹数据
1 | const e = await browser.storage.local.get("browserId"); |
Inject scripts
这是自造的非官方概念,复习一下这个概念
由于Content scripts与网页隔离,有些功能又需要访问网页空间中的数据,扩展程序往往使用会在Content scripts中通过DOM注入script标签执行一些代码,这个脚本被我称之为Inject scripts
1 | let script = document.createElement('script') |
Inject scripts与网页中的其他JS同等权限,由执行顺序决定地位,Inject scripts只有在第一个运行才是安全的,不然一切都是不可信的
在Content scripts中可以指定运行时间,未定义会默认使用document_idle
document_start在DOM创建之前document_endDOM完成之后,会早于img和iframe加载document_idle在end之后,在onload之前
那么可以整理时间线
document_start脚本- 网页脚本
DOMContentLoaded事件document_end脚本document_idle脚本load事件
脚本的执行顺序决定了地位,只有在document_start运行脚本使用Inject scripts才是安全的,否则脚本使用的函数可能会被恶意的网页脚本hook,导致数据泄露
但document_start这个时机,对于大多数扩展并不“实用”,此时DOM还没有创建,只有一个空的HTML标签
而且需要直接在documentElement追加script
1 | let script = document.createElement('script') |
但可以预先建立安全通信信道,注册一些功能,等待网页加载完成后再执行更多的功能,这块可以参考duckduckgo的安全实现
https://github.com/duckduckgo/content-scope-scripts
另外之前提到在Manifest V3中禁止代码执行和加载远程代码能力,所以script.src不允许远程地址,也不能通过script.textContent方式执行代码,全都会被CSP拦截
script.src只能是扩展程序自身的js文件路径
1 | let script = document.createElement("script"); |
但此时inject.js在网页空间中执行,不再受Manifest V3 CSP限制,而是受网页CSP限制,大部分网页CSP并没有那么严格,往往能够执行引入外部js甚至执行eval
有些扩展程序用这个这个方式来规避严格的Manifest V3 CSP,但也因此让Manifest V3扩展有了UXSS的可能,也算是一个“寄”巧了
Wappalyzer
msg->Inject->Content->Background->Popup
扩展脚本需要在网页空间查找一些特定的JS变量,来识别相关的框架/库,所以部分检测逻辑是在Inject scripts中实现的
其中检测规则通过Content的postMessage传输给Inject,Inject执行的结果又通过postMessage回传给Content
这里网页JS可以通过伪装成Inject,给Content发消息,实现欺骗指纹识别
详细可以参考我的老文章 https://blog.xlab.app/p/63a5b7e6/
Kaspersky (CVE-2019-15687)
在注入的脚本会在在window中创建变量
1 | if (ns.WORK_IDENTIFIERS) |
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 | { |
在Manifest V3中稍微修改了定义格式,增强了访问控制,可以进一步定义在哪些url中可访问
1 | { |
并增加了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中监听window的message事件,且没有检查来源,根据msg中传入的数据,卸载指定的Chrome扩展程序
同时这个页面在WAR中,于是可以通过iframe加载,并给这个页面发消息,实现卸载任意的Chrome扩展程序
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 | <script type="module" src="chrome-extension://fheoggkfdfchfphceeifdbepaooicaho/block_page.js"> |
引入settings.js,实现修改配置,同时这个配置与本机WebAdvisor程序自动同步
于是将payload同步注入到WebAdvisor程序,实现在程序设置页面XSS,之后进一步利用程序设置页面功能实现修改注册表和RCE
1 | <script type="module" src="chrome-extension://fheoggkfdfchfphceeifdbepaooicaho/settings.js"> |
这个漏洞入口虽然没有涉及WAR,但告诉我们WAR可以作为XSS gadget存在
Externally Connectable
前面提到只有Content能和Background通信,而网页是不能的,但Externally Connectable开了这个口子
externally_connectable在manifest.json中以白名单的形式定义哪些扩展或网页可以与自己通信
1 | { |
另外默认情况下扩展和扩展之间可以相互访问,设置externally_connectable时同时配置"ids": ["*"]才能保持这个默认配置
1 | { |
扩展程序在接收消息时不同于接收来自Content的请求,需要使用chrome.runtime.onMessageExternal来接受消息

Custom Cursor
ExtMag->Background
指定域名可与扩展通信,通过调用Background功能,传递的Msg直接通过jQuery渲染HTML,导致XSS
1 | collection = $( |
同时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,开启摄像头,上传视频等功能
在无交互的情况下能够实现
- 开启摄像头录屏
- 上传视频到Google Drive
- 用Google OAuth access token获取视频
同样利用网站XSS也能实现
https://palant.info/2022/05/23/hijacking-webcams-with-screencastify/
Chrome API
一些Chrome API的数据源也是用户输入
比如chrome.tabs系列,可以获取标签页的URL,Title等
还有chrome.cookie,chrome.contextMenus等
由于没有找到漏洞案例,这里不展开了
攻击链路图
以此绘制完整的攻击链路图
- 黑色为直接可控入口
- 灰色是关键API模块
- 红色是最终目标
- 链接线是数据链路,链接文字是相关权限要求/安全防护
