
针对 Firefox 149.0a1 nightly(2fbc0748c4,修复前)中沙箱逃逸的概念验证。受入侵的 content 进程伪造一条 IPDL 消息,使 parent 进程将页面加载到 特权(privilegedabout)进程中。
这是第二阶段沙箱逃逸,设计用于在 content 进程 RCE(例如 CVE-2026-2796)之后进行链接利用。
Parent 进程从 content→parent 的 IPDL 消息中反序列化 nsDocShellLoadState。存在漏洞的构建版本不会拒绝 content 设置的 RemoteTypeOverride 字段,因此 content 可以选择导航最终落入的进程类型。
修复(commit 3ec5c71b41bb,"Validate RemoteTypeOverride")使 nsDocShellLoadState 构造函数拒绝在 content 触发的加载上使用任何覆盖值。
当覆盖值被采纳时 —— 对于 about:blank 的文档加载,顶级上下文 —— IsolationOptionsForNavigation 会将加载的 remote type 设置为覆盖值,页面被放入特权进程。沙箱边界被突破。
该覆盖值搭载在 PNecko::PDocumentChannel 上,这是每个 content 进程在每次文档加载时都会发送的消息(DocumentChannelCreationArgs.loadState 是一个 nsDocShellLoadState)。
投递过程是最有趣的部分:直接写入 IPC socket 会被静默丢弃,因为 IPDL 消息现在运行在 mojo ports 之上。相反,PoC 通过原生调用(利用 CVE-2026-2796 的 funcref 原语)从 JavaScript 驱动真实的发送路径:
operator new — 为 IPC::Message 分配一个堆块IPC::Message::Message(routing, type, capacity, flags) — placement 构造Pickle::WriteBytes — 追加伪造的负载MessageChannel::Send — mojo 帧、端口路由和序列号均由原生侧处理负载(由 forge.py 构建)携带 RemoteTypeOverride = "privilegedabout"、URI about:blank、空主体(null principal),以及运行时泄露的值(BrowsingContext id、TriggeringRemoteType)。
./irun
Parent 侧日志(MOZ_LOG=ProcessIsolation:5,DocumentChannel:5)显示切换到特权进程的过程:
V/ProcessIsolation using remote type override (privilegedabout) for load
I/ProcessIsolation Process Switch: Changing Remoteness from 'webIsolated=http://127.0.0.1' to 'privilegedabout'
3ec5c71b41bb "Validate RemoteTypeOverride"| 文件 | 用途 |
|---|
privesc.html | PoC 页面,在受入侵的 content 进程内运行 |
forge.py / forge.json | 构建伪造的 IPDL 消息(自校验) |
wasm-bytes.js | 第一阶段原语:任意读写、funcref 调用 |
mdrive.py / mdrive2.py | 用于启动并驱动 Nightly 的 marionette 测试工具 |
irun | 插桩运行:lldb 附加到 parent + MOZ_LOG 证据 |
profile.user.js | Firefox profile 偏好(fission 开启,dump 启用) |
parse_dc.py | 对捕获的 DocumentChannel 消息进行字节级精确解析 |
scan_sentinels.py / extract_fields.py | 用于捕获数据的字段映射扫描器 |