跳至主要内容

突破壁垒:使用沙箱强化应用程序

·阅读时间:4 分钟

自从 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 设置时,这同样会禁用沙箱。这是可以理解的:如果您长期使用 Electron,您可能很喜欢在运行 HTML/CSS 代码的相同代码中使用 require("child_process")require("fs") 的强大功能。

在讨论如何迁移到沙箱之前,让我们先讨论一下为什么要使用它。

沙箱会在所有渲染器进程周围设置一个坚固的“笼子”,确保无论内部发生什么,代码都在受限的环境中执行。从概念上讲,它比 Chromium 的历史更悠久,并且是所有主要操作系统提供的功能。Electron 和 Chromium 的沙箱构建在这些系统功能之上。即使您从未显示过用户生成的内容,也应该考虑渲染器可能遭到入侵的可能性:从供应链攻击等复杂场景到小错误等简单场景,都可能导致您的渲染器执行您并非完全希望它执行的操作。

沙箱使这种情况不那么可怕:进程内部可以自由使用 CPU 周期和内存 - 就这样。进程无法写入磁盘或显示自己的窗口。对于我们的 libwep 错误,沙箱可确保攻击者无法安装或运行恶意软件。事实上,在最初对该员工 iPhone 的 Pegasus 攻击中,攻击者专门针对非沙箱图像进程以获取手机访问权限,首先突破了通常处于沙箱状态的 iMessage 的边界。当像此示例中这样的 CVE 发布时,您仍然需要将 Electron 应用升级到安全版本 - 但同时,攻击者可以造成的损害程度会大大降低。

将普通的 Electron 应用程序从 sandbox: false 迁移到 sandbox: true 是一项艰巨的任务。我知道,因为即使我个人编写了 Electron 安全指南 的第一版草稿,我也没有设法将我自己的一个应用程序迁移为使用它。这种情况在本周末发生了改变,我建议您也进行更改。

Don’t be scared by the number of line changes, most of it is in package-lock.json

您需要解决两件事

  1. 如果您在 preload 脚本或实际的 WebContents 中使用 Node.js 代码,则需要将所有 Node.js 交互移至主进程(或者,如果您很熟悉,则移至实用程序进程)。鉴于渲染器功能变得多么强大,很有可能您的绝大多数代码实际上并不需要重构。

    查阅我们关于 进程间通信 的文档。在我的例子中,我移动了很多代码,并将其包装在 ipcRenderer.invoke()ipcMain.handle() 中,但这个过程很简单,很快就完成了。这里要注意一下您的 API - 如果您构建了一个名为 executeCodeAsRoot(code) 的 API,那么沙箱将无法很好地保护您的用户。

  2. 由于启用沙箱会禁用 preload 脚本中的 Node.js 集成,因此您不能再使用 require("../my-script")。换句话说,您的 preload 脚本需要成为单个文件。

    有多种方法可以做到这一点:Webpack、esbuild、parcel 和 rollup 都可以完成这项工作。我使用了 Electron Forge 出色的 Webpack 插件,同样流行的 electron-builder 的用户可以使用 electron-webpack

总而言之,整个过程大约花费了我四天时间 - 这其中包括很多时间用来思考如何驾驭 Webpack 的强大功能,因为我决定利用这个机会以其他多种方式重构我的代码。