Chrome 远程代码执行漏洞(CVE-2025-6554)
Chrome 远程代码执行漏洞(CVE-2025-6554)
前言
2025年6月,谷歌威胁分析小组(Clement Lecigne, @_clem1)发现一个在野漏洞利用,该漏洞利用了V8引擎中一个非常著名的原始漏洞,并引入了一种新的利用技术——另一个“the_hole”泄漏问题,引发了广泛关注。该漏洞本身非常有趣,涉及V8引擎内部的多个领域和概念。
Google于2025年6月30日发布了最新的 Chrome 安全通告,公告链接https://chromereleases.googleblog.com/2025/06/其中修复了高危漏洞2025-6554,并且已经知道该漏洞已经存在在野利用。
[NA][427663123] High CVE-2025-6554: Type Confusion in V8.
Reported by Clément Lecigne of Google’s Threat Analysis Group on 2025-06-25.
This issue was mitigated on 2025-06-26 by a configuration change
pushed out to Stable channel across all platforms.
Google is aware that an exploit for CVE-2025-6554 exists in the wild.
漏洞时间线
- 2025年6月30日:Google 修复漏洞并确认存在在野利用
- 2025年7月2日:研究员 @DarkNavyOrg 在 X 平台公开 PoC
- 修复版本:Commit
22e9d9621de58ec6fe6581b56215059a48451b9f
环境搭建
这里选择commit编号为609a85c2a1bd77d6f6905369f4bc4fcf34c5db09的v8环境。
然后通过chromiumdash查询这个commit合并的chrome对应的版本,也就是下面这个链接
https://chromiumdash.appspot.com/commit/609a85c2a1bd77d6f6905369f4bc4fcf34c5db09
/image-20251224165752816.png)
然后可以知道对应的稳定版本也就是stable版本为140.0.7339.41。chromiumdash查询一下对应的branch编号为
https://chromiumdash.appspot.com/fetch_version?version=140.0.7339.41
/image-20251224165923332-1766566964394-3.png)
前置知识
TDZ
暂时性死区(TDZ)指的是:
在代码块内,使用let或const声明的变量,在声明语句之前就被访问时,JavaScript会抛出ReferenceError异常,而不是返回undefined
示例1:
1 | function test(){ |
执行逻辑:
1.JS引擎会在编译阶段发现let a;
2.它会为a创建绑定,但不会像var那样赋初值
3.进入该块作用域后,a已存在但未初始化
4.当执行到console.log(a)时,因为a还没被初始化,所以处于TDZ,进而抛出error
示例2:
1 | function test(){ |
var声明的变量会:
1.在作用域顶部变量提升
2.自动初始化为undefined
3.因此不会出现TDZ
operator ?.
示例1:
1 | let obj ={ inner: { val: 42 } }; |
执行流程:
1.计算 obj?.inner?.val
2.若在访问链中,任何 ?. 前的值为 null 或 undefined,则delete表达式立即返回 ‘true’。例如当 obj.inner 为 null 时,表达式将变为 delete undefined.val,随后返回 true。
3.否则执行常规删除逻辑
示例1等价于:
1 | if (obj == null) return true; |
注:
- 可选链式操作符是一种安全的访问运算符
- 当发生短路时,它不会抛出 TypeError 异常
- 删除操作仅适用于属性访问,无法删除使用 let 或 const 声明的变量
The Hole
- Hole 是 V8 引擎内部的特殊值,用于表示数组/对象中的”空位”(如
[1,,3]中的中间元素) - 运行时访问hole将触发运行时错误或检查逻辑。
漏洞原理
1. POC分析
1 | function f() { |
2. 关键技术点解析
1) 可选链操作符(Optional Chaining)?.
- 可选链的语义是:如果左侧表达式为
null或undefined,则短路返回undefined,不会访问后续属性 - 在 PoC 中:
x为undefined→x?.[y]应该返回undefined - V8 在字节码生成阶段,遇到
x为undefined时会通过JumpIfUndefinedOrNull指令跳转,绕过对y的访问
2) “Hole” 类型的本质
- Hole 是 V8 引擎内部的特殊值,用于表示数组/对象中的”空位”(如
[1,,3]中的中间元素) - Hole 本不应该暴露给 JavaScript 用户代码,但通过特定构造可以让其”泄露”出来
- 历史上通过 Hole 实现代码执行的案例包括 CVE-2023-3079
3) 暂时性死区(Temporal Dead Zone, TDZ)
- 使用
let/const声明的变量在初始化前无法访问,访问会抛出ReferenceError - 在 PoC 中:
y在delete表达式中被使用,但其let y声明在后面 - V8 内部通过将 TDZ 变量的值设为 Hole 来实现这一机制,当检测到 Hole 时会调用
ThrowReferenceErrorIfHole抛出错误
3. 漏洞触发流程
1 | ├─ x = undefined (未初始化) |
结果:函数返回了本不应暴露的 Hole 值
漏洞分析
v8 sandbox
还记得前面提到过,需要在编译v8的时候修改编译选项,将args.gn中的v8_enable_sandbox置为false(默认为true)。它的作用是什么呢?当它为true的时候,将开启v8 sandbox,否则关闭。参考v8 sandbox。简单来讲,它的作用是创建一个沙箱,即使出现了漏洞,也不能直接执行代码,而需要再穿越这个沙箱才行。如果打开这个开关,我们的POC还会执行成功吗?答案是不能。区别在于:
1 | function write_shell_code(rwx_addr, shellcode) { |
这段代码中,通过addressof(shellArray) + 0x2c存放的是Uint8Array对象数组的指针,它是一个full pointer,将它修改为rwx内存的地址,Uint8Array就指向了rwx,对Uint8Array数组访问,就可以直接修改rwx内容了。当v8_enable_sandbox 为true时。addressof(shellArray) + 0x2c存放的是v8 heap addr。这是一个压缩指针,中间记录的是偏移。addr + base << 32才能得到真正的地址,base存放在寄存器中。由于base存放在寄存中,无法泄露,也无法修改,因此无法构造一个合理的跳板来写rwx,进一步让漏洞无法利用。这是v8 sandbox想要达到的效果。也就是说,以后要实现一个完成的漏洞利用链,除了v8的漏洞外,还需要额外找到一个v8 sandbox的漏洞才行。用%DebugPrint查看不同开关下面Uint8Array的结构。
1 | # disable v8 sandbox |
关闭sandbox,external_pointer为一个完整的指针。addressof(shellArray) + 0x2c存的值是0x7fb90870c630。替换为rwx的值即可读写rwx里面的内容。
1 | # enable v8 sandbox |
开启sandbox,0x76e00000000为v8 sandbox addr,addressof(shellArray) + 0x2c存的值是0x100000000,寄存器里面存放的是0x76d,两者相加得到0x76e00000000。
过往the_hole漏洞利用技术
the_hole对象已成为V8中反复出现的漏洞利用原语,攻击者发现了多种将其泄漏到JavaScript并利用它进行内存破坏的方法。在CVE-2025-6554之前,至少有两个值得注意的在野漏洞利用案例使用了the_hole:
CVE-2022-1364:逃逸分析绕过
该技术利用了V8逃逸分析实现中的疏漏。漏洞根源在于非标准的getThis API在节点逃逸分析过程中未能被正确追踪,导致the_hole对象可被泄漏至JavaScript环境。
一旦成功泄漏,攻击者便能利用the_hole的内存布局操控Map对象:
1 | function getmap(m) { |
CVE-2023-2033:Turbofan类型混淆漏洞
@mistymntncop 发现的CVE-2023-2033漏洞利用揭示了Turbofan类型系统中存在一个缺陷:the_hole被意外地当作其他Oddball对象处理,使得像ToNumber这样的操作能够返回NaN。这种意外行为导致了JIT编译器中的类型混淆:
1 | function weak_fake_obj(b, addr=1.1) { |
CVE-2025-6554
CVE-2025-6554代表了一种不同的攻击途径。与之前针对逃逸分析或类型系统行为的漏洞利用不同,该漏洞利用了Ignition字节码生成器中的作用域生命周期管理缺陷,特别是围绕TDZ(暂时性死区)空洞检查省略优化机制。其泄漏后的利用技术同样具有创新性,通过消除TurboFan加载消除阶段的TypeGuard验证来绕过类型检查,从而创建具有非法长度的数组。
漏洞根源
该漏洞源于V8引擎中Ignition字节码生成器的作用域生命周期管理缺陷,具体表现为在可选链控制流边界处跟踪TDZ空洞检查省略优化机制时出现错误。
V8的暂时性死区机制
JavaScript 的 let 和 const 声明会创建一个暂时性死区,在该区域中变量虽已存在于作用域内,但在声明之前无法被访问:
1 | console.log(x); // ReferenceError: Cannot access 'x' before initialization |
V8内部使用名为the_hole的特殊标记值来标识未初始化的变量。在每次访问暂时性死区变量前,V8会抛出ThrowReferenceErrorIfHole字节码指令。2023年6月,V8启用了一项优化机制,通过位图追踪技术在同一基本块内消除冗余的暂时性死区检查。
漏洞利用
32位的情况下的利用
1 | function hax(trigger) { |
首先构造这样的函数。然后通过大量执行实现JIT优化。
1 | let normal = hax(false); |
通过hax函数获取到了the_hole之后,就有了越界地址读写的方法了。于是构造如下数组
1 | let corrupted = hax(true); |
通过计算调试得出flt_map的地址和obj_map的地址
1 | let flt_arr_map_full = corrupted[17] |
然后再构造addrof的功能
1 | function addrof(in_obj) { |
然后通过越界读写可以实现任意地址读写,当然在64位的情况下因为指针压缩的原因,只能在4GB的范围读写。32位下就没有这样的限制了。
1 | function arb_read(in_addr){ // bigint |
最后按照v8的利用逻辑用wasm去利用即可,首先先泄露shellwasm_rwx的地址。
需要注意的是,在64位的情况下的123版本后的chrome开启了v8 sandbox,堆的沙箱保护,trusted_data_ptr这个值做了保护,无法直接泄露出来,需要绕过,这里我们使用的是32位的,就不存在这个保护。
1 |
|
然后用DataView实现读写,先泄露buf_backing_store_addr的地址,然后将其修改为shell_wasm_rwx_addr即可。
这里需要注意的是buf_backing_store_addr在开启了指针压缩的情况下,大概在v8的10点几版本后就对buf_backing_store_addr也开启了指针压缩,于是无法实现任意地址读写,只能局限在4GB的空间中。但这里我们使用的32位就没有这个限制。
1 |
|
最后写入shellcode再执行即可
1 |
|
未给出的工具函数如下
1 |
|
最后实现的效果就是弹出计算器
/e831c2b0594e3c4757d1157d229149a0.jpg)
-



