Skip to main content

从漏洞到防护:通过沙箱强化应用

· 8 min read

自从CVE-2023-4863:WebP中的堆缓冲区溢出被公开以来,已经过去一周多,这导致了一系列新的软件发布,用于渲染 webp 图片:macOS、iOS、Chrome、Firefox 以及各种 Linux 发行版都收到了更新。这是在公民实验室的调查之后发现的,他们发现一个由“位于华盛顿特区的民间社会组织”使用的 iPhone 正在通过 iMessage 中的零点击漏洞遭受攻击。

🌐 It’s been more than a week since CVE-2023-4863: Heap buffer overflow in WebP was made public, leading to a flurry of new releases of software rendering webp images: macOS, iOS, Chrome, Firefox, and various Linux distributions all received updates. This followed investigations by Citizen Lab, discovering that an iPhone used by a “Washington DC-based civil society organization” was under attack using a zero-click exploit within iMessage.

Electron 也迅速行动起来,并在同一天发布了新版本:如果你的应用呈现任何用户提供的内容,你应该更新你的 Electron 版本——v27.0.0-beta.2、v26.2.1、v25.8.1、v24.8.3 和 v22.3.24 都包含修复后的 libwebp,该库负责渲染 webp 图片。

🌐 Electron, too, spun into action and released new versions the same day: If your app renders any user-provided content, you should update your version of Electron - v27.0.0-beta.2, v26.2.1, v25.8.1, v24.8.3, and v22.3.24 all contain a fixed version of libwebp, the library responsible for rendering webp images.

现在我们都清楚地意识到,像“渲染图片”这样简单的交互也可能存在危险,我们想借此机会提醒大家,Electron 内置了一个进程沙盒,它可以限制下一次大规模攻击的波及范围 - 无论它是什么。

🌐 Now that we are all freshly aware that an interaction as innocent as “rendering an image” is a potentially dangerous activity, we want to use this opportunity to remind everyone that Electron comes with a process sandbox that will limit the blast radius of the next big attack — whatever it may be.

自从 Electron v1 开始,沙箱功能就已经可用,并且在 v20 中默认启用,但我们知道许多应用(尤其是那些存在已久的应用)可能在它们的代码中某处有一个 sandbox: false —— 或者一个 nodeIntegration: true,当没有明确的 sandbox 设置时,也会同样禁用沙箱。这是可以理解的:如果你和我们一起走过了很长时间,你可能享受过将 require("child_process")require("fs") 扔进运行 HTML/CSS 的同一代码中的强大功能。

🌐 The sandbox was available ever since Electron v1 and enabled by default in v20, but we know that many apps (especially those that have been around for a while) may have a sandbox: false somewhere in their code – or a nodeIntegration: true, which equally disables the sandbox when there is no explicit sandbox setting. That’s understandable: If you’ve been with us for a long time, you probably enjoyed the power of throwing a require("child_process") or require("fs") into the same code that runs your HTML/CSS.

在我们讨论你如何迁移到沙盒之前,先让我们讨论一下你为什么想要这样做。

🌐 Before we talk about how you migrate to the sandbox, let’s first discuss why you want it.

沙箱在所有渲染进程周围设置了一个严格的隔离环境,确保无论内部发生什么,代码都在受限的环境中执行。作为一个概念,它比 Chromium 要早得多,并且作为功能由所有主要操作系统提供。Electron 和 Chromium 的沙箱构建在这些系统功能之上。即使你从未显示用户生成的内容,也应考虑渲染器可能被攻破的可能性:从复杂的供应链攻击到简单的小漏洞,都可能导致渲染器执行你未完全预期的操作。

🌐 The sandbox puts a hard cage around all renderer processes, ensuring that no matter what happens inside, code is executed inside a restricted environment. As a concept, it's a lot older than Chromium, and provided as a feature by all major operating systems. Electron's and Chromium's sandbox build on top of these system features. Even if you never display user-generated content, you should consider the possibility that your renderer might get compromised: Scenarios as sophisticated as supply chain attacks and as simple as little bugs can lead to your renderer doing things you didn't fully intend for it to do.

沙盒使这种情况的威胁大大降低:内部的进程可以自由使用 CPU 周期和内存——仅此而已。进程不能写入磁盘或显示自己的窗口。在我们提到的 libwep 漏洞案例中,沙盒确保攻击者无法安装或运行恶意软件。实际上,在最初针对员工 iPhone 的 Pegasus 攻击中,攻击者专门针对一个非沙盒化的图片处理进程,以获取手机访问权限,首先突破了通常沙盒化的 iMessage 的边界。当像本例中的 CVE 漏洞被公布时,你仍然需要将 Electron 应用升级到安全版本——但与此同时,攻击者能造成的损害量会大幅度受限。

🌐 The sandbox makes that scenario a lot less scary: A process inside gets to freely use CPU cycles and memory — that’s it. Processes cannot write to disk or display their own windows. In the case of our libwep bug, the sandbox makes sure that an attacker cannot install or run malware. In fact, in the case of the original Pegasus attack on the employee’s iPhone, the attack specifically targeted a non-sandboxed image process to gain access to the phone, first breaking out of the boundaries of the normally sandboxed iMessage. When a CVE like the one in this example is announced, you still have to upgrade your Electron apps to a secure version — but in the meantime, the amount of damage an attacker can do is limited dramatically.

将一个原生 Electron 应用从 sandbox: false 迁移到 sandbox: true 是一项大工程。我知道,因为即使我个人撰写了 Electron 安全指南 的初稿,我也未能将自己的应用迁移到使用它。这个周末情况有所改变,我也建议你进行相应的更改。

🌐 Migrating a vanilla Electron application from sandbox: false to sandbox: true is an undertaking. I know, because even though I have personally written the first draft of the Electron Security Guidelines, I have not managed to migrate one of my own apps to use it. That changed this weekend, and I recommend that you change it, too.

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

你需要解决两件事:

🌐 There are two things you need to tackle:

  1. 如果你在 preload 脚本或实际的 WebContents 中使用 Node.js 代码,你需要将所有 Node.js 的交互移到主进程(或者,如果你比较高级的话,可以移到一个辅助进程)。考虑到渲染器已经非常强大,很有可能你大部分代码实际上并不需要重构。

    请查阅我们关于进程间通信的文档。在我的案例中,我移动了大量代码并将其封装在 ipcRenderer.invoke()ipcMain.handle() 中,但整个进程很简单,也很快完成。在这里要稍微注意一下你的 API ——如果你创建一个名为 executeCodeAsRoot(code) 的 API,沙箱并不能为你的用户提供太多保护。

  2. 由于启用沙箱会在预加载脚本中禁用 Node.js 集成,因此你无法再使用 require("../my-script")。换句话说,你的预加载脚本需要是一个单独的文件。

    有多种方法可以实现这一点:Webpack、esbuild、parcel 和 rollup 都能完成任务。我使用了 Electron Forge 出色的 Webpack 插件,同样受欢迎的 electron-builder 用户可以使用 electron-webpack

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

🌐 All in all, the entire process took me around four days — and that includes a lot of scratching my head at how to wrangle Webpack’s massive power, since I decided to use the opportunity to refactor my code in plenty of other ways, too.