进程沙盒化

Chromium的一个关键安全特性是,进程可以在沙盒中执行。 沙盒通过限制对大多数系统资源的访问来减少恶意代码可能造成的伤害 — 沙盒化的进程只能自由使用CPU周期和内存。 为了执行需要额外权限的操作,沙盒处的进程通过专用通信渠道将任务下放给更大权限的进程。

在Chromium中,沙盒化应用于主进程以外的大多数进程。 其中包括渲染器进程,以及功能性进程,如音频服务、GPU 服务和网络服务。

查阅Chromium的 沙箱设计文档 了解更多信息。

从 Electron 20 开始,渲染进程默认启用了沙盒,无需进一步配置。 如果你想禁用某个进程的沙盒,请参阅为单个进程禁用沙盒部分。

Electron 中的沙盒行为

在 Electron 中沙盒进程 大部分地 表现都与 Chromium 差不多, 但因为介面是 Node.js 的关系 Electron 有一些额外的概念需要考虑。

渲染器进程

当 Electron 中的渲染进程被沙盒化时,它们的行为与常规 Chrome 渲染器一样。 一个沙盒化的渲染器不会有一个 Node.js 环境。

因此,在沙盒中,渲染进程只能透过 进程间通讯 (inter-process communication, IPC) 委派任务给主进程的方式, 来执行需权限的任务 (例如:文件系统交互,对系统进行更改或生成子进程) 。

进程沙盒化 - 图1note

想要了解更多关于进程间通信的信息,请参阅我们的 IPC 指南

Preload 脚本

为了让渲染进程能与主进程通信,附属于沙盒化的渲染进程的 preload 脚本中仍可使用一部分以 Polyfill 形式实现的 Node.js API。 有一个与 Node 中类似的 require 函数提供了出来,但只能载入 Electron 和 Node 内置模块的一个子集:

  • electron (以下是渲染进程的模块: contextBridge, crashReporter, ipcRenderer, nativeImage, webFrame)
  • 事件
  • timers
  • url

Node. js 中的import方法也是被支持的:

此外,以下 Node.js 基础对象也填充到了 preload 脚本的全局上下文中:

require 函数只是一个功能有限的 Ployfill 实现,并不支持把 preload 脚本拆成多个文件然后作为 CommonJS 模块 来加载。 若需要拆分 preload 脚本的代码,可以使用 webpackParcel 等打包工具。

注意,因为 preload 脚本的运行环境本质上比沙盒化渲染进程的拥有更高的特权,除非开启了 contextIsolation,否则高特权的 API 仍有可能被泄漏给渲染进程中的不信任代码。

配置沙盒

对于大多数应用程序来说,沙盒是最佳选择。 在某些与沙盒不兼容的使用情况下(例如,在渲染器中使用原生的 Node.js 模块时),可以禁用特定进程的沙盒。 但这会带来安全风险,特别是当未受信任的代码或内容存在于未沙盒化的进程中时。

为单个进程禁用沙盒

在 Electron 中,可通过在 BrowserWindow 构造函数中使用 sandbox: false选项来针对每个进程禁用渲染器沙盒。

main.js

  1. app.whenReady().then(() => {
  2. const win = new BrowserWindow({
  3. webPreferences: {
  4. sandbox: false
  5. }
  6. })
  7. win.loadURL('https://google.com')
  8. })

在渲染器中启用 nodeIntegration 时,沙盒也会被禁用。 可以通过在 BrowserWindow 构造函数中添加 nodeIntegration: true 标志的来实现。

main.js

  1. app.whenReady().then(() => {
  2. const win = new BrowserWindow({
  3. webPreferences: {
  4. nodeIntegration: true
  5. }
  6. })
  7. win.loadURL('https://google.com')
  8. })

全局启用沙盒

你也可以调用 app.enableSandbox API 来强制沙盒化所有渲染器。 注意,此 API 必须在应用的 ready 事件之前调用。

main.js

  1. app.enableSandbox()
  2. app.whenReady().then(() => }
  3. // 因为调用了app.enableSandbox(),所以任何sandbox:false的调用都会被覆盖。
  4. const win = new BrowserWindow()
  5. win.loadURL('https://google.com')
  6. })

禁用 Chromium 的沙盒(仅测试)

你也可以指定 --no-sandbox 命令行参数来完全禁用 Chromium 的沙盒功能,这会使沙盒对所有进程失效(包括工具进程)。 我们强烈建议你只针对测试用途开启此标志,并且 永远 不要用于生产环境。

注意,sandbox: true 选项也会同时禁用渲染进程中的 Node.js 环境。

渲染不可信内容的注意事项

尽管已经有一些成功案例(例如 Beaker 浏览器),但在 Electron 中渲染不受信任的内容仍有未知的风险。 我们的目标是尽可能达到与 Chrome 中沙盒化的内容一样的安全性,但由于一些现实因素还没法做到:

  1. 我们不像 Chromium 团队那样在产品安全方面有专属的资源与专业知识。 虽然已经尽可能地继承 Chromium 中的一切,并且尽快响应安全问题,但缺少 Chromium 可调动的那些资源,我们做不到和它一样安全。
  2. Chrome 的一些安全特性(例如安全浏览和证书透明度)依赖于中心化授权和专属服务器,这些都超出了 Electron 项目的目标。 因此我们在 Electron 中禁用了它们,同时也损失了它们带来的安全性。
  3. Chromium 只有一个,但基于 Electron 构建的应用却成千上万,并且千差万别。 这些差异带来了太多的可能性,很难在各种特殊的应用场景下都保证平台的安全。
  4. 我们没法向终端用户直接推送安全更新,只能靠应用供应商更新依赖的 Electron 版本,来让更新覆盖到用户。

虽然我们会尽可能将 Chromium 的安全修复应用到老版本的 Electron 中,但没法保证每一个修复都能移植过去。 为了保证安全,最好的办法还是始终使用最新的稳定版 Electron。