Skip to main content

突破屏障:使用沙盒增强应用

· 10 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 的修复版本,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: falsenodeIntegration: 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 交互移至主进程(或者,如果你喜欢,也可以移至实用程序进程)。鉴于渲染器已经变得如此强大,你的绝大多数代码很可能不需要重构。

    ¥If you’re using Node.js code in either preload scripts or the actual WebContents, you need to move all that Node.js interaction to the main process (or, if you are fancy, a utility process). Given how powerful renderers have become, chances are high that the vast majority of your code doesn’t really need refactoring.

    请参阅我们关于 进程间通信 的文档。就我而言,我移动了大量代码并将其打包到 ipcRenderer.invoke()ipcMain.handle() 中,但这个过程很简单,很快就完成了。请注意你的 API - 如果你构建了一个名为 executeCodeAsRoot(code) 的 API,沙盒并不能很好地保护你的用户。

    ¥Consult our documentation on Inter-Process Communication. In my case, I moved a lot of code and wrapped it in ipcRenderer.invoke() and ipcMain.handle(), but the process was straightforward and quickly done. Be a little mindful of your APIs here - if you build an API called executeCodeAsRoot(code), the sandbox won't protect your users much.

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

    ¥Since enabling the sandbox disables Node.js integration in your preload scripts, you can no longer use require("../my-script"). In other words, your preload script needs to be a single file.

    有多种方法可以解决此问题:Webpack、esbuild、parcel 和 rollup 都可以完成这项工作。我使用了 Electron Forge 优秀的 Webpack 插件,同样流行的 electron-builder 的用户可以使用 electron-webpack

    ¥There are multiple ways to do that: Webpack, esbuild, parcel, and rollup will all get the job done. I used Electron Forge’s excellent Webpack plugin, users of the equally popular electron-builder can use 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.