结合漏洞案例,分析各组件的攻击面,最终绘制完整的攻击链路图
漏洞特点
攻击场景设计是在用户在访问恶意站点,触发漏洞利用
前端漏洞主要还是围绕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 scripts
Background 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中使用存储有
localStorage
sessionStorage
indexedDB
Web SQL
如果在Content
中使用Web Storage,其实是在使用当前网站的存储,而不是扩展程序自己的
写入Web Storage存在信息泄露,同时读取Web Storage也是用户输入
有时使用Web Storage并不是扩展本身的意愿,而是扩展程序使用的依赖库在使用Web Storage
在Background
中,由于有自己的页面,所以是存储在扩展中,不过在Chrome扩展中推荐使用的Chrome API提供的存储能力
chrome.storage.local
chrome.storage.sync
chrome.storage.session
chrome.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_end
DOM完成之后,会早于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可以转化为Background
XSS,可以无视同源策略访问所有网站
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模块
- 红色是最终目标
- 链接线是数据链路,链接文字是相关权限要求/安全防护