突破屏障:使用沙盒增强应用
自 CVE-2023-4863:WebP 中的堆缓冲区溢出 公开以来已经超过一周了,这导致了大量软件渲染 webp
图像的新版本发布:macOS、iOS、Chrome、Firefox 和各种 Linux 发行版都收到了更新。此前,Citizen Lab 进行了调查,发现一个“位于华盛顿特区的公民社会组织”使用的 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
设置时,它同样会禁用沙盒。这是可以理解的:如果你和我们在一起很长时间了,你可能已经享受了将 require("child_process")
或 require("fs")
放入运行 HTML/CSS 的同一代码中的强大功能。
在我们讨论如何迁移到沙盒之前,我们先讨论一下为什么你需要它。
沙盒在所有渲染器进程周围设置了一个硬性隔离区,确保无论内部发生什么,代码都在受限环境中执行。作为一个概念,它比 Chromium 早得多,并且作为所有主要操作系统的一项功能提供。Electron 和 Chromium 的沙盒构建在这些系统功能之上。即使你从不显示用户生成的内容,你也应该考虑你的渲染器可能被入侵的可能性:供应链攻击这样复杂的场景以及小错误这样简单的情况都可能导致你的渲染器执行你完全不希望它执行的操作。
沙盒使这种情况变得不那么可怕:内部进程可以自由使用 CPU 周期和内存——仅此而已。进程无法写入磁盘或显示自己的窗口。在我们 libwep
漏洞的情况下,沙盒确保攻击者无法安装或运行恶意软件。事实上,在最初的 Pegasus 攻击员工 iPhone 的案例中,攻击专门针对未沙盒化的图像进程以获得对手机的访问权限,首先突破了通常沙盒化的 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 的强大功能,因为我决定借此机会以多种其他方式重构我的代码。