突破屏障:使用沙箱加强应用程序
自 CVE-2023-4863:WebP 中的堆缓冲区溢出 公开以来,已经过去了一周多的时间,这导致了大量渲染 webp
图像的软件新版本发布:macOS、iOS、Chrome、Firefox 和各种 Linux 发行版都收到了更新。此前,公民实验室进行了调查,发现一个“位于华盛顿特区的民间组织”使用的 iPhone 在 iMessage 中遭到零点击漏洞攻击。
Electron 也立即采取行动,并在同一天发布了新版本:如果您的应用程序渲染任何用户提供的内容,您应该更新您的 Electron 版本 - v27.0.0-beta.2、v26.2.1、v25.8.1、v24.8.3 和 v22.3.24 都包含 libwebp
的修复版本,该库负责渲染 webp
图像。
现在我们都清楚地意识到“渲染图像”这种看似无辜的交互是一种潜在的危险活动,我们想借此机会提醒大家,Electron 自带一个进程沙箱,它将限制下一次重大攻击的波及范围——无论它是什么。
沙箱自 Electron v1 以来就可用,并在 v20 中默认启用,但我们知道许多应用程序(尤其是那些已经存在一段时间的应用程序)可能在其代码中的某个地方存在 sandbox: false
- 或 nodeIntegration: true
,当没有明确的 sandbox
设置时,也会禁用沙箱。这是可以理解的:如果您已经和我们在一起很长时间了,您可能已经习惯了在运行 HTML/CSS 的同一代码中抛出 require("child_process")
或 require("fs")
的强大功能。
在我们讨论如何迁移到沙箱之前,让我们先讨论一下您为什么需要它。
沙箱在所有渲染器进程周围设置了一个硬性限制,确保无论内部发生什么,代码都在受限环境中执行。作为一个概念,它比 Chromium 早得多,并且由所有主要操作系统作为一项功能提供。Electron 和 Chromium 的沙箱建立在这些系统功能之上。即使您从不显示用户生成的内容,您也应该考虑您的渲染器可能被入侵的可能性:供应链攻击等复杂的场景和诸如小错误之类的简单场景都可能导致您的渲染器执行您并非完全打算让它执行的操作。
沙箱使这种情况变得不那么可怕:内部的进程可以自由使用 CPU 周期和内存 - 仅此而已。进程无法写入磁盘或显示自己的窗口。在我们的 libwep
错误的情况下,沙箱确保攻击者无法安装或运行恶意软件。事实上,在最初对员工的 iPhone 发动的 Pegasus 攻击中,攻击专门针对一个未沙箱化的图像进程来获取对手机的访问权限,首先突破了通常沙箱化的 iMessage 的边界。当宣布像本例中的 CVE 时,您仍然必须将您的 Electron 应用程序升级到安全版本 - 但与此同时,攻击者可以造成的损害量会大大减少。
将一个普通的 Electron 应用程序从 sandbox: false
迁移到 sandbox: true
是一项艰巨的任务。我知道,因为即使我亲自编写了Electron 安全指南的初稿,我也未能将我自己的一个应用程序迁移到使用它。这种情况在本周末发生了变化,我建议您也更改它。
您需要解决两个问题
-
如果您在
preload
脚本或实际的WebContents
中使用 Node.js 代码,则需要将所有 Node.js 交互移动到主进程(或者,如果您喜欢的话,移动到实用程序进程)。鉴于渲染器已经变得如此强大,您的绝大多数代码都不真正需要重构的可能性很高。请参阅我们关于进程间通信的文档。在我的例子中,我移动了很多代码,并将其包装在
ipcRenderer.invoke()
和ipcMain.handle()
中,但该过程很简单且很快就完成了。请注意这里的 API - 如果您构建了一个名为executeCodeAsRoot(code)
的 API,则沙箱将无法很好地保护您的用户。 -
由于启用沙箱会禁用预加载脚本中的 Node.js 集成,因此您不能再使用
require("../my-script")
。换句话说,您的预加载脚本需要是单个文件。有多种方法可以做到这一点:Webpack、esbuild、parcel 和 rollup 都可以完成这项工作。我使用了 Electron Forge 出色的 Webpack 插件,同样流行的
electron-builder
的用户可以使用electron-webpack
。
总而言之,整个过程花了我大约四天的时间 - 这包括在如何处理 Webpack 的强大功能方面花费了大量时间,因为我决定利用这个机会以多种其他方式重构我的代码。