导语:Google 9 月 3 日紧急推送 Chrome 152.0.7977.82 补丁,修复一个被野外利用的 V8 类型混淆漏洞 CVE-2026-85046。这是 Chrome 在 2026 年第六个公开的零日漏洞,也是年内第三个直指 V8 引擎本身的类型混淆。攻击者只需一个 HTML 页面就能在 V8 沙箱内执行任意代码,触发逻辑不超过十行 JavaScript。 (关键词简注:V8 是 Chrome 浏览器的 JavaScript 与 WebAssembly 执行引擎;Maglev 与 Turbofan 是 V8 内部的两个分层即时编译器;类型混淆指程序把一种对象当成另一种对象使用,从而造成内存破坏)

一、漏洞速览
CVE-2026-85046 的官方描述很简洁:“Chrome 152.0.7977.82 之前的 V8 引擎存在类型混淆漏洞,远程攻击者可通过构造的 HTML 页面在沙箱内执行任意代码。” CVSS(通用漏洞评分系统)给了 8.8 分的高危评级,CWE(通用缺陷枚举)分类为 CWE-843(访问资源时使用了不兼容的类型)。该漏洞由意大利安全研究员 Salvatore Gulizia(网名 Serotav)于 8 月 4 日上报,Google 给了 1000 美元漏洞赏金。
但数字背后藏着几个让红队兴奋的信号:
- 9 月 3 日披露,9 月 4 日即被 CISA(美国网络安全与基础设施安全局)纳入 KEV(已知被利用漏洞)目录,联邦机构修复截止日 9 月 18 日。KEV 通常只收录真正有人在用的漏洞,这种”次日上 KEV”的节奏,说明情报机构掌握的攻击样本远早于补丁发布。
- 这是 2026 年 Chrome 第六个被野外利用的零日,且连续第三个针对 V8 引擎本身。前两个分别是 CVE-2026-11645(6 月)和 CVE-2026-5281(4 月),密度惊人。
- 修复提交哈希 e0562d87ad9c17042b581582c99237d798572e67 的修补量极小,仅在 Maglev 与 Turbofan 的内联排序入口各加 10 余行守卫代码。这种”加一个前置条件就能修”的漏洞,恰恰说明攻击面之深——一个被遗漏的边界条件让整个优化路径成了沙箱穿透的跳板。
二、漏洞机理:类型系统为何被攻破
要理解这个漏洞,需要先了解 V8 内部对 JavaScript 数组的”元素类型”分类机制。
V8 在每个数组对象的隐藏类(Map)里维护一个叫 ElementsKind(元素类型)的属性,用于告诉 JIT(即时编译器)”这个数组里装的是什么”。最常见的三种类型形成一条单向迁移链:
PACKED_SMI_ELEMENTS(紧凑小整数) ──→ PACKED_DOUBLE_ELEMENTS(紧凑浮点数) ──→ PACKED_ELEMENTS(任意对象)
箭头方向不能反过来——一旦数组里出现了对象,Map 就升级到 PACKED_ELEMENTS,再也不会退回 SMI。唯一的例外是 Array.prototype.fill(),当它把所有元素替换成更”窄”类型时,V8 会顺势把 Map 也回退到更具体的类型,因为它认为既然内容都变了,Map 跟着降级是安全的。
CVE-2026-85046 利用的就是这个”例外”。
V8 在 4 月 27 日引入了内联 Array.prototype.sort 优化(提交 66a3f1e94d4b681bff6476a876067a3c79a853f0)。对长度 ≤ 16 的小数组,Maglev 与 Turbofan 会把 sort 调用替换成一段内联的快速排序代码,大致长这样:
1. 检查 Map 在已知集合内
2. 把数组元素复制到临时 FixedArray(定长数组)
3. 对临时数组跑插入排序,比较函数由用户传入
4. 再次检查 Map 仍在已知集合内
5. 把排序后的临时数组写回原数组
问题出在第 4 步。已知 Map 集合是”我之前训练时见过的所有 Map”,如果训练阶段同时见过 SMI 数组和对象数组,这个集合就同时包含 PACKED_SMI_ELEMENTS 与 PACKED_ELEMENTS 两个 Map。比较函数里只要调一句 arr.fill(0),arr 的 Map 就会被回退到 PACKED_SMI_ELEMENTS——而这个 Map 正好在已知集合里,所以守卫检查通过。第 5 步于是把仍然装着对象指针的临时数组,写进一个声称”只装小整数”的数组里。
类型系统,从此被攻破。
补丁的修法非常优雅:在内联排序的入口加一条前置条件,所有候选 Map 必须拥有相同的 ElementsKind。只要训练阶段见过混合类型,就拒绝内联,回退到通用 sort 内置函数。这条改动在 Turbofan 的 js-call-reducer.cc 与 Maglev 的 maglev-graph-builder.cc 各加 11 行与 10 行,外加 89 行回归测试,干净利落。
三、攻击链:从类型混淆到任意读写
从类型混淆到真正能控制程序执行,还需要几步加工。研究员 Serotav 在他的博客里给出了完整的最小化复现,整条链路不到 30 行 JavaScript。
第一阶段:JIT 训练。反复调用 confuse([1,2,3]) 与 confuse([{},{},{}]) 各 1000 次左右,让 Maglev 收集到”两种元素类型都见过”的反馈,从而为后续内联铺路。整个训练阶段在现代硬件上不到一秒。
第二阶段:触发类型混淆。构造一个对象数组 [{}, {}],传给 confuse。比较函数里的 arr.fill(0) 会让 V8 把 Map 回退到 PACKED_SMI_ELEMENTS,守卫检查放行,写回完成。此时数组的 Map 声称只装小整数,但背后的 FixedArray 槽位里依然是指向真实对象的压缩指针(V8 默认开启指针压缩,所有堆内指针都按 32 位偏移存储)。
第三阶段:取地址原语(addrof)。这一步利用了 V8 一个微妙的实现细节:当一个数组 Map 说是 SMI 时,字符串化操作会按”小整数”的方式解读每个槽位——也就是把对象的压缩指针当成整数读出来。研究员用 String(arr).split(‘,’) 拿到这些”假整数”,左移一位再加上标签位 1,就把任意 JS 对象的堆地址泄漏了出来。
第四阶段:伪造对象原语(fakeobj)。这一步更难。SMI 数组写入时会跳过 V8 的写屏障(GC 即垃圾回收器用来追踪指针存写的机制),这正是攻击者想要的。研究员把数组先 unshift 一个 Smi,让原始的对象指针被右移,新的 Smi 槽位就成了可以任意写的”假整数”。把这个假整数填进一个双精度浮点数组的相邻槽位,再用 fakeobj 把这个槽位当成对象指针读回来,就拿到了一个完全受控的伪对象引用。
第五阶段:构造伪 JSArray。用 addrof 拿到一个合法双精度浮点数组的 Map 地址,再把 Map + Properties + Elements + Length 四个字段按伪 JSArray 的结构手工拼到一个双精度浮点数组的 backing store(底层存储区)里。把 fakeobj 指过去,一个 elements 指针可任意指定的伪数组就诞生了。通过修改伪数组的 elements 指针偏移,就能在整个 4 GB V8 沙箱内做任意读写。
到这里,已经具备 V8 沙箱内的全部能力——读任意 JS 对象、改任意对象字段、伪造任意对象结构、绕过 GC 写屏障。要逃出 V8 沙箱拿到完整 RCE(远程代码执行),还需要一个独立的沙箱逃逸漏洞,比如篡改 WebAssembly 调度表项或滥用 JSPI 栈清理不一致。研究员本人在博客末尾提到,他已经把类型混淆 + 一个 N 日沙箱逃逸组合起来打穿了 v8CTF 的题目。
四、POC 下载与实战复现
完整的 POC 已经由 atiilla 在 GitHub 上公开,仓库包含两个核心文件:
- poc-minimal.js:纯 d8(V8 调试版运行时)触发的最小验证脚本,无需任何依赖。
- poc.html:浏览器全流程利用链,依次演示 addrof、堆喷射、fakeobj、信息泄漏五个阶段,并把每个阶段的关键地址打到页面与开发者控制台。
最小复现只需两步:
# 方法一:d8 直接跑(推荐,速度快)
d8 --allow-natives-syntax poc-minimal.js
# 方法二:浏览器跑(需要 Chrome < 152.0.7977.82)
# 用未修补的 Chrome 打开 poc.html,观察页面上的地址泄漏日志
仓库地址:https://github.com/atiilla/CVE-2026-85046
POC 的稳定度极高——训练阶段 1000 次循环在毫秒级完成,类型混淆是确定性的(无需竞态、无需复杂的堆布置),addrof 原语通过 String() 转换直接生效,跨多次运行的地址一致(V8 沙箱内无 ASLR 即地址空间布局随机化)。唯一的不可控因素是 fakeobj 阶段的堆喷射,需要 256+ 次固定大小数组分配来制造双精度/对象数组 backing store 相邻布局。在现代硬件上整个利用链 1 秒内跑完,浏览器标签页崩溃风险极低。
需要强调的是,POC 目前只能做到 V8 沙箱内的任意读写,距离完整 RCE 还差一个沙箱逃逸。但对于已经在 0day 市场上活跃的 exploit broker(漏洞利用中间商)来说,沙箱逃逸属于”独立产品线”,组合打法早已成熟。这个 POC 的真正威胁,是给勒索软件团伙、间谍软件供应商、商业监控厂商提供了一条成本极低的 V8 入口——他们手头大概率已经备好了逃逸漏洞。
五、检测与缓解
对个人与企业:
- Chrome 立即升级到 152.0.7977.82(Linux)或 152.0.7977.82/.83(Windows/macOS),所有基于 Chromium 的浏览器(Edge、Brave、Opera、Vivaldi)以及 Electron 应用都需要同步更新。CISA 给出的最后期限是 9 月 18 日,但攻击窗口每多开一天,被针对性攻击的概率就高一分。
- 企业终端可以临时通过组策略锁定浏览器版本,或在 NGFW(下一代防火墙)/ WAF(Web 应用防火墙)侧拦截 poc.html 的特征——不过这种 PoC 指纹很容易被攻击者改写。
对蓝队:
- 关注浏览器子进程的异常行为。V8 类型混淆利用通常会让渲染进程在崩溃前产生异常堆栈,关键词包括 Array.prototype.sort、CheckMaps、IteratingArrayBuiltinHelper 等。
- 部署具备内存破坏检测能力的 EDR(端点检测与响应)产品,例如对 Chromium 渲染进程启用堆栈 canary 与控制流完整性检查。
- 对所有托管 Web 内容做 JavaScript 静态扫描,重点排查短时间大量 Array.prototype.sort 与 Array.prototype.fill 交替调用的可疑脚本——这是 POC 的训练阶段特征。
对红队:
这个漏洞的价值不在于完整 RCE,而在于它揭示了 V8 内联优化反复踩同一个坑:JIT 编译器在”读操作”上放宽类型边界是对的(加载兼容子类型没问题),但在”写操作”上套用同一条规则就是灾难(写回窄类型但 backing store 没重分配)。CVE-2024-4947、CVE-2025-2135 犯的是同类错误,今后针对 V8 的内联优化路径,依然是 JIT 误用 lattice(类型格)假设的重灾区。
六、写在最后
CVE-2026-85046 的故事本身并不复杂——一个边界条件、一个写屏障漏洞、一个 lattice 假设失效。但它折射出的趋势令人警惕:Chrome 2026 年才过去三分之二,已经有六个零日漏洞被野外利用,其中三个直指 V8 类型系统。V8 团队即使连续修补,攻击者也持续在 JIT 编译器的边界条件下挖金子。
下一次”Chrome 紧急推送”的警报响起时,红队的反应应该和这次一样:先把补丁 diff 看一遍,定位被引入的提交,再去搜同类边界条件——CVE-2026-85046 这种”加几行守卫就修”的洞,往往不是孤例。














暂无评论内容