evilCallback: CVE-2021-21225
CVE-2016-1646
在进行这个分析中,可以先看一个比较旧的漏洞 CVE-2016-1646
,在执行
的时候,v8会使用
遍历concat()
的每一个对象并且传入IterateElements
,对于一个只有double
元素的数组,它的elementsKind
属于FAST_DOUBLE_ELEMENTS
这时,在IterateElements
会进入如下分支
在这里可以发现,数组的length
被缓存下来,然后使用fast_length
来控制循环次数,但如果遇到一个hole
型的元素,就会使用JSReceiver::GetElement(isolate, array, j), false
从原型链查找,这个时候会触发getter/setter
callback,这里可以执行任意的JavaScript,如果在这个callback中
就可以导致数组长度变短,并且经过垃圾回收,空闲的内存将被再次分配到其它对象,然而fast_length
并没有被修改,导致越界读
CVE-2021-21225
从上面我们可以知道,如果在遍历过程中能够触发callback并且修改掉数组长度,就很有可能是不安全的,观察patch可以发现,patch其实就是重新引入了CVE-2021-21225
,在visit()
中,有
这又会调用Maybe<bool> Object::SetDataProperty(LookupIterator* it, Handle<Object> value)
,
在value
不是Number
的时候,会进一步调用Object::ToNumber
这会触发valueOf
callback,接下来我们只需要能进入外层的if,这里我们需要让receiver为一个TypedArray
,这可以通过Symbol.species
来实现,concat
会通过Symbol.species
来构造需要返回的对象,
这样我们就可以让它返回一个TypedArray
了,然后通过valueOf
callback,触发数组长度缩减
Build our primitive
Info leak
在v8中,literal array的储存方式为
Low High
[element 0][element 1]...[element n][Map][Property][Elements][Length]
所以通过越界读,我们可以泄漏出Map
, Property
和Elements
,从而计算出其它相邻对象的地址
arbitrary r/w
首先我们要能够得到一个最基本的任意读写能力,通过以上的分析,我们可以轻松泄漏出相邻对象的地址,接下来我们考虑构造一个fake object,它的类型为Float Array,这在第一步的时候,通过泄漏的Map,Property,Elements,Length就可以实现,我们把这些泄漏数据存入fake_object_arr的buffer中,接下来它的buffer就会成为这个fake object。 对于一个混合类型的数组
将会获得对应元素的指针,如果我们通过callback缩减数组长度,并进行垃圾回收,从而让这个目标数组中hole
的位置与另外一个存有我们fake_object地址的地址重叠,这个时候elements->get(j)
就会返回我们的fake_object对象
这里,我们让addr和hole在垃圾回收后重叠,就可以获得一个以fake_object_arr_buffer_addr
为地址的对象了,并且这个对象的属性可以通过fake_object
来进行任意的控制,我们就获得了基础的任意读写能力,然而这里有一个问题,就是此时我们的对象其实是一个浮点数组,根上面的分析,这里会尝试将这个数组转换为Number
,这会抛出异常,如果在原型链上再加一个callback,通过抛出异常我们就可以顺利退出这个函数,并且把得到的fake_object保存下来
接下来构造任意读写
通过fake_object
我们可以控制target_array
的属性,fake_object[2]
就是target_array
的elements
指针,由此我们就实现了任意读写。
address of
在 JavaScript 引擎的漏洞利用中一个很重要的 primitive 就是 address of,让我们获取任意对象的地址,考虑
arr是一个混合数组,其中存储的是对象的指针,我们将对象存入arr[0]
再通过任意读读出这个位置的值,就能知道任意对象的地址了。
Code execution
有两种方法,在 JavaScript 引擎的利用中我们可以利用 wasm 或者 JIT 产生的 rwx 代码段写 shellcode,也可以靠普通的glibc方法(这里不同的库版本会有影响),泄漏 glibc 地址,写 freehook 拿到任意代码执行,
通过map我们可以拿到可执行文件的基址,然后通过GOT表拿到libc地址
然后写freehook为system
,最后利用console.log
会释放参数,执行任意命令
enjoy your shell