CVE-2024-5830:从Chrome渲染器中的对象转换到远程代码执行
本文详细分析了Chrome浏览器V8 JavaScript引擎中的一个类型混淆漏洞(CVE-2024-5830)。该漏洞允许攻击者仅通过访问恶意网站,即可在Chrome的渲染器沙箱中实现远程代码执行(RCE)。漏洞于2024年5月报告,已在Chrome 126.0.6478.56/57版本中修复。
核心概念:V8中的对象映射(Map)与转换
V8使用“映射”(或隐藏类)来描述对象的内存布局并优化属性访问。具有相同属性布局的对象共享同一个映射。当对象添加新属性或属性类型改变时,会发生“映射转换”,旧映射与新映射通过转换关系连接,并且旧映射可能被标记为“已弃用”(deprecated)。
当访问一个拥有已弃用映射的对象时,V8会调用UpdateImpl函数更新其映射。该函数通过回溯指针找到根映射,然后沿着转换路径寻找一个合适的、更通用的新映射。然而,如果一个映射无法再容纳新的转换,UpdateImpl会通过Normalize操作创建一个字典映射(dictionary map,属性存储在字典中,与快速映射的数组存储不同)。
漏洞成因:快速与字典映射的意外混淆
问题出现在PrepareForDataProperty函数中。当通过CreateDataProperty(例如,使用扩展语法{...obj}进行对象克隆)为对象添加属性时,该函数会调用Update来更新对象的映射。
漏洞触发路径:攻击者可以构造一个场景,在对象克隆过程中,通过属性访问器(getter)的副作用,迫使被克隆对象(y)的映射变为已弃用。随后,通过精心操作,使得另一个对象(obj)的映射在添加过多转换后,无法容纳新转换。此时,当Update被调用以更新y的映射时,它会意外地返回一个字典映射。
关键点在于,后续的PrepareForDataProperty函数代码假设更新后的映射是快速映射,并据此访问了仅对快速映射有效的instance_descriptors(实例描述符数组)。对于字典映射,该字段是一个空的只读数组。对该数组的越界读写,最终导致了内存损坏。
利用过程:从类型混淆到任意代码执行
获得V8堆上的任意读写:
- 通过上述机制,攻击者可以覆盖字典映射的内部
elements字段,使其指向一个较大的值。
- 当触发对象的“慢速到快速”迁移(
MigrateSlowToFast)时,该被污染的elements字段会被用作遍历属性的边界,导致越界读取。
- 通过精心安排堆布局,攻击者可以使越界读取的值指向一个预先伪造的对象(例如,一个具有巨大长度的双精度数组)。
- 利用这个伪造的数组,攻击者可以进一步在V8堆内获得任意读写能力。
突破V8堆沙箱:
V8堆沙箱将V8堆与进程的其他内存(如代码段)隔离。为了实现RCE,需要绕过此沙箱。
- Chrome中的Web API对象(如DOM对象)由Blink引擎实现,存在于V8堆之外,并在V8中以“API对象”表示。这些对象包含“嵌入器字段”,其中存储了指向实际Blink对象的指针(通过外部指针表间接引用)。
- 利用在V8堆内获得的任意读写能力,攻击者可以替换一个API对象的嵌入器字段,使其指向另一个不同类型的Blink对象(例如,将
DOMRect的字段替换为DOMTypedArray的字段),从而在Blink层面引发类型混淆。
- 通过这种类型混淆,攻击者可以读写任意Blink对象的内存。具体地,通过混淆
DOMRect和DOMTypedArray,可以覆盖DOMTypedArray的backing_store_指针,从而获得对整个进程地址空间的任意读写能力。
实现任意代码执行:
- 攻击者可以利用任意内存读能力,读取
TrustedCage::base_等全局静态对象的地址,以击败ASLR。
- 最后,通过修改一个JIT编译函数的代码指针,将其指向攻击者控制的恶意代码,从而在Chrome渲染器进程中实现任意代码执行。
结论
CVE-2024-5830展示了V8引擎中映射转换机制的一个细微缺陷。当已弃用映射的更新过程意外产生字典映射,而后续代码未能正确处理这种类型变化时,就会导致类型混淆。攻击者利用这一混淆,逐步升级特权,从V8堆内的内存损坏,到堆外的任意内存访问,最终实现了浏览器渲染器进程中的远程代码执行。这凸显了JavaScript引擎中复杂优化路径所带来的安全风险。