Electron Fuses
包特性切换
什么是 fuses ?
从安全角度上看,禁用某些未使用的 Electron 特性是明智之举,这些特性虽然功能强大,但有可能削弱应用程序的安全防护能力。 例如,任何不使用 ELECTRON_RUN_AS_NODE 环境变量的应用都应该禁用这一特性以防范一部分“离地”攻击。
我们也不想让 Electron 用户通过分叉来实现这一目标,因为从源代码开始构建以及维护分叉代码都是巨大的技术挑战,会消耗大量的时间和金钱。
Fuse 正是这一问题的解决方案。 从高层级来看,它们是 Electron 二进制文件里的“魔法位”,在打包 Electron 应用时可以被翻转以启用或禁用特定的特性/限制。
因为它们是在打包时被翻转的,之后就要对应用进行代码签名,所以操作系统就得负责通过系统级代码签名验证来确保这些位不会被翻转回去(比如 macOS 的 Gatekeeper 或者 Windows 的 AppLocker)。
当前可用的 Fuse
runAsNode
默认值: 启用
@electron/fuses:FuseV1Options.RunAsNode
runAsNode Fuse 用于切换是否遵循 ELECTRON_RUN_AS_NODE 环境变量。 禁用了这个 Fuse 后,主进程里的 child_process.fork 将无法正常工作,因为它依赖这个环境变量才能运作。 我们推荐你使用实用进程来取代它,其适用于许多需要独立 Node.js 进程的使用场景(比如 SQLite 的服务器进程)。
cookieEncryption
默认值: 禁用
@electron/fuses:FuseV1Options.EnableCookieEncryption
cookieEncryption Fuse 用于切换硬盘上存储的 Cookie 是否使用系统级加密密钥进行加密。 默认情况下,Chromium 用来存储 Cookie 的 SQLite 数据库是用明文形式来存储 Cookie 的值的。 如果你想确保你的应用的 Cookie 被加密,就如同 Chrome 所做的一样,那么你就应该启用这个 Fuse。 请注意这是单向转换—如果你启用这个 Fuse,已有的未加密 Cookie 将会在写入时被加密,但如果在此之后禁用这个 Fuse,那就会使你的 Cookie 存储损坏,无法读取。 大部分应用都可以安全地启用这个 Fuse。
nodeOptions
默认值: 启用
@electron/fuses:FuseV1Options.EnableNodeOptionsEnvironmentVariable
nodeOptions Fuse 用于切换是否遵循 NODE_OPTIONS 环境变量和 NODE_EXTRA_CA_CERTS 环境变量。 NODE_OPTIONS 环境变量可用于向 Node.js 运行时传递各种自定义选项,通常生产环境下的应用不会用到这个变量。 大部分应用都可以安全地禁用这个 Fuse。
nodeCliInspect
默认值: 启用
@electron/fuses:FuseV1Options.EnableNodeCliInspectArguments
nodeCliInspect Fuse 用于切换是否遵循 --inspect,--inspect-brk 等标志。 禁用后,它还会确保 SIGUSR1 信号不会初始化主进程检查器。 大部分应用都可以安全地禁用这个 Fuse。
embeddedAsarIntegrityValidation
默认值: 禁用
@electron/fuses:FuseV1Options.EnableEmbeddedAsarIntegrityValidation
embeddedAsarIntegrityValidation Fuse 用于切换在 macOS 和 Windows 上,加载 app.asar 时是否验证其内容。 该特性旨在将性能影响降至最低,但仍有可能略微降低从 app.asar 归档文件内部读取文件的速度。 大部分应用都可以安全地启用这个 Fuse。
关于如何使用 ASAR 完整性验证的更多信息,请参阅 ASAR 完整性文档。
onlyLoadAppFromAsar
默认值: 禁用
@electron/fuses:FuseV1Options.OnlyLoadAppFromAsar
onlyLoadAppFromAsar Fuse 用于改变 Electron 用来寻找你的应用代码的搜索系统。 默认情况下,Electron 将会按照下列顺序寻找代码:
app.asarappdefault_app.asar
启用了这个 Fuse 后,Electron _只_会寻找 app.asar 。 如果搭配 embeddedAsarIntegrityValidation Fuse 使用,这个 Fuse 能够确保无法加载未经验证的代码。
loadBrowserProcessSpecificV8Snapshot
默认值: 禁用
@electron/fuses:FuseV1Options.LoadBrowserProcessSpecificV8Snapshot
V8 快照有助于提升应用启动性能。 V8 引擎能让你抓取已初始化的堆的快照,之后再把它们加载回来以避免初始化堆的开销。
loadBrowserProcessSpecificV8Snapshot Fuse 用于设置浏览器进程使用哪个 V8 快照文件。 默认情况下,Electron 的进程都会使用相同的 V8 快照文件。 启用了这个 Fuse 后,主进程将会使用名为 browser_v8_context_snapshot.bin 的文件作为它的 V8 快照。 其他进程将会使用它们通常使用的 V8 快照文件。
为渲染进程和主进程分别使用单独的快照能够提高安全性,尤其能确保渲染器不会使用启用了 nodeIntegration 的快照。 更多详情请看 electron/electron#35170。
grantFileProtocolExtraPrivileges
默认值: 启用
@electron/fuses:FuseV1Options.GrantFileProtocolExtraPrivileges
grantFileProtocolExtraPrivileges Fuse 用于切换通过 file:// 协议加载的页面是否会获得超出传统 Web 浏览器所赋予的权限。 在原始版本的 Electron 中,这种行为是 Electron 应用的核心特性,但是现在已经不再需要了,因为现在的应用应该通过自定义协议提供本地文件。
如果你不通过 file:// 提供页面,你应该禁用这个 Fuse。
由这个 Fuse 赋予 file:// 协议的额外权限包括但不限于:
file://协议页面可以使用fetch通过file://加载其他资源。file://协议页面可以使用 Service Worker。file://协议页面会向同样运行在file://协议上的子框架赋予全局访问权限,而不论沙箱设置如何。
wasmTrapHandlers
默认值: 启用
@electron/fuses:FuseV1Options.WasmTrapHandlers
wasmTrapHandlers Fuse 用于控制 V8 是否使用信号处理程序拦截来自 WebAssembly 的越界内存访问。 该特性的原理是在 WebAssembly 内存周围设置大块防护区域,接着安装信号处理程序,拦截对防护区域内存的访问尝试。 该特性仅在下列 64 位操作系统上受支持:
- Linux,macOS,Windows - x86_64
- Linux,macOS - aarch64
| 防护页 | WASM 堆 | 防护页 |
|-----8GB-----| |-----8GB-----|
禁用了这个 Fuse 后,V8 将会在生成的 WebAssembly 代码中采取明确的边界检查以确保内存安全。 然而,这个方法有一些缺点
- 编译器会为每个内存引用生成额外的结点,由于这些结点需要额外的处理时间,编译时间会更长。
- 这些额外的结点反过来生成了大量额外的代码,使得 WebAssembly 模块比理想情况的要大。
- 这些额外代码,尤其是每个内存引用之前的比较和分支代码,会产生相当大的运行时开销。
我该如何翻转 Fuse?
简易方式
@electron/fuses 是一个 JavaScript 实用模块,能够轻松地翻转这些 Fuse。 要了解使用方法以及潜在的错误用法,请参阅该模块的 README。
const { flipFuses, FuseVersion, FuseV1Options } = require('@electron/fuses')
flipFuses(
// Electron 的路径
require('electron'),
// 要翻转的 Fuse
{
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false
}
)
你可以使用 @electron/fuses 命令行工具验证任意 Electron 应用的 Fuse 是否已经被翻转或者检查其 Fuse 状态。
npx @electron/fuses read --app /Applications/Foo.app
[!NOTE] 如果你正在使用 Electron Forge 分发你的应用程序,你可以使用
@electron-forge/plugin-fuses翻转 Fuse,所有的模板都会预安装这一插件。
复杂方式
[!IMPORTANT] 术语:
- Fuse Wire:Electron 二进制文件内用于控制 Fuse 的字节序列
- Sentinel:可用于定位 Fuse Wire 的已知固定字节序列
- Fuse Schema:Fuse Wire 的格式/允许值
手动翻转 Fuse 需要编辑 Electron 二进制文件并修改 Fuse Wire 为代表你所需要的 Fuse 状态的字节序列。
在 Electron 二进制文件的某处,会有一串看起来像这样的字节序列:
| ...binary | sentinel_bytes | fuse_version | fuse_wire_length | fuse_wire | ...binary |
sentinel_bytes永远都是这串固定字符串:dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX。fuse_version是单个字节,其无符号整数值代表 Fuse Schema 的版本fuse_wire_length是单个字节,其无符号整数值代表紧跟其后的 Fuse Wire 里的 Fuse 数量。fuse_wire是由 N 个字节组成的序列,每个字节代表单个 Fuse 及其状态。- "0"(0x30)表示 Fuse 已禁用
- "1"(0x31)表示 Fuse 已启用
- "r"(0x72)表示 Fuse 已经被移除,将字节更改为 1 或 0 将不会有任何作用。
要翻转一个 Fuse,你需要在 Fuse Wire 中找到它的位置并根据你需要的状态将其修改为“0”或“1”。
你可以在这里查看当前的 Fuse Schema。