1:写在前面:
最近老是做一些根据文档和代码构造POC的事情,经常想着怎么锻炼这一方面的能力。以v8为例,如果让别人直接指出某个地方有问题,然后让我去找出来,构造POC以至于EXP,以我现在对v8的熟练程度,简直是开玩笑。
于是秉着循序渐进的原则,想到了高中做物理题目,就把这个过程理解为高中做开放物理课题的过程。
先假设有个出题人,给出了所有满足解题的条件,然后尝试根据这些题目给出的提示解出正确的答案。秉着这个思路给自己设计了一个练习方案
这次设计的练习主要参考的是这篇文章
https://tiszka.com/blog/CVE_2021_21225.html
选择这篇文章做练习的原因
1:这位师傅对这一系列漏洞触发的成因和条件描写得令人发指的详细。
2:这个系列的漏洞本人以往只是做过利用,对漏洞的原因和触发的条件却是一知半解了,很适合目前的我用来练习构造v8 POC的课题。
先假设这篇文章所说的内容就认为是解题需要的所有条件。
当然,不需要跟某些大佬设置的CTF考试一样闭卷考……可以搜索互联网上POC和EXP除外的所有知识要点。
尝试根据文中给出代码提示和自己掌握的和搜索的知识构造出POC。
最后看看根据自己的理解和实验构造出的POC,和原文给出的POC有什么区别。
因为包含三个漏洞,所以没有构建v8环境进行调试,也没有看源代码,其实就这个目的来说也没太大必要,
如果只说针对构造漏洞触发的条件的话,原文的代码片段及解释已经足够的详细。
因此只有下载相应版本的Chrome来测试是否能走到触发漏洞为止。
虽然讲的是POC的构造,不过这里还是先介绍这系列漏洞利用的原理吧,毕竟知道怎么玩,才有足够的动力去挖。
1.1:Array.prototype.concat函数漏洞。
这系列的漏洞公告介绍是越界读导致的RCE,一般的越界读漏洞只能获得信息泄露或转换为任意读,但这一系列漏洞却是可以通过越界读来获得RCE。
其中的关键点就是巧妙的利用v8的GC机制,来往我们可以索引到的数组元素里面“写进“我们预先构造好的数组地址,来伪造我们可以完全控制的数组,从而获得任意读写的能力,具体过程如下。
1.2:Array.prototype.concat函数漏洞的利用原理:
1.2.1:假设我们拥有这样一个浮点数组var A = [1.1,2.2,3.3,4.4];
其在v8的内存布局中是这样的: (图 1.2.1.1所表示的,在指针压缩引入之前的v8,浮点数组A内存布局,引入之后的A内存布局有些区别,但是在这里的漏洞利用的原理都是一样),
图 1.2.1.1
假设这个时候length变成了1,并且触发了GC回收,v8一般不会在原内存中重建或保存数组A,相反会在一块新的内存地址中,重建数组A,并更新内存布局,情况会如图 1.2.1.2所示
图 1.2.1.2
假设这个过程中出现问题数组长度没有变化,length依旧为4,那么重建的数组A,这情况就如同图 1.2.1.3,这种情况下我们就可以通过A[1]越界索引到map,能通过A[3]能索引到elements,泄露出重要的内存地址。
图 1.2.1.3
1.2.2:假设我们原本的数组 var A=[1.1,2.2,3.3,4.4,5.5,{}];,也如上述所讲的情况,将数组length修改为1,触发垃圾CG回收情况,情况就会如图 1.2.2.1所示:
图 1.2.2.1
这时候如果length实际上没有更新的话,如图1.2.2.2,我们提前设置好的地址会代替对象{}的地址,因为A[5]原本指向为一个{}对象,所以我们可以通过A[5]索引,把我们预先布局好的对象地址,索引为{}对象。
图 1.2.2.2
通过在预先布局的地址构造好我们的伪造对象数据,可以实现完全控制一个对象,再通过这个完全控制的对象,进一步实现v8进程内存的任意读写,结合前面的信息泄露,就凑够了v8 RCE需要的所有原语。
这里漏洞利用有个重要技巧,涉及到v8的CG机制,在作者的
https://tiszka.com/blog/CVE_2021_21225_exploit.html
writeup里面有详细说明,要了解这漏洞的RCE技巧,以及想了解v8 RCE和CG知识的,建议细读。
二:关于Array.prototype.concat()出现的历史漏洞
2.1: CVE-2016-1646
2.1.1 CVE-2016-1646 root case
漏洞发生在函数Array.prototype.concat(),简单分析一下代码要点
图 2.1.1.1
图2.1.1.1代码所示: Array.prototype.concat的返回放置[1]所示的visitor对象。args表示的是参与函数运算的参数,接下来的循环,需要会对每个参数args->at(i)进行了IteratorElements运算。
图2.1.1.2
图2.1.1.2 : [3]可以看到在IterateElement运算中,会将图 2.1.1.1过程中中的args->at(i)保存在array这个变量之中,然后在[4]过程中将数组array的长度放置在变量length上,并且对数组的类型ElementsKind进行判断,以便接下来做相应的处理。
图2.1.1.3
图2.1.1.3 : 在ElementsKind判断之后在[7]中将前面[4]的length的值赋值到fast_length变量中,并用于接下来的FOR_WITH_HANDLE_SCOPE的循环。(也就是说array在Array.prototype.concat运算中返回的数组长度在,这个过程中已经不会再变化,为fast_length)
图 2.1.1.4
图 2.1.1.4:
在FOR_WITH_HANDLE_SCOPE的循环中,[9]中会取出数组的element,然后[10]判断element是否为hole,
如果为hole则会调用JSReceiver::GetElement(isolate,array,j),并调用visitor->visit(j,element)来返回结果。
但是问题是JSReceiver::GetElement(isolate,array,j)为v8的Slow运算,该过程会触发Getter回调。
我们可以通过在回调中写入任意的JS代码来触发越界读。
具体的触发漏洞的做法前面已经说过,将array的length减小,然后触发垃圾回收。因为我们用于FOR_WITH_HANDLE_SCOPE循环的fast_length实际上并没有发生变化,所以结果会读取length之后的数值回去。
2.1.2 构造POC
2.1.2.1综合POC构造需要满足所有条件
条件1:创建FAST_DOUBLE_ELEMENTS类型的数组 A;
条件2:进入element_value->IsTheHole(isolate) 的代码流程。
条件3:在hole元素中。执行JSReceiver::GetElement触发Getter回调。
条件4:在回调函数之中减小A数组的长度,并触发CG。
条件5: 当然,最后一步需要执行Array.prototype.concat函数,才能走进前面4个条件的执行代码流程。
2.1.2.2 POC的构造;
条件1:创建FAST_DOUBLE_ELEMENTS类型的数组
代码:
条件2:要满足element_value->IsTheHole(isolate)
这个条件要求在条件1里创建的数组A上创建一个hole,满足ElementValue->IsTheHole(),才会走到条件3的JSReceiver::GetElement分支流程触发回调。
代码:
https://stackoverflow.com/questions/61420580/can-anyone-explain-v8-bytecode-ldathehole
条件3:在hole元素中执行JSReceiver::GetElement触发Getter回调,因此这步构造为
因为JSReceiver::GetElement实际上是遍历A的原型,所以这一步构造为:
代码:
条件4:在回调函数之中减小A数组的长度,并触发CG。
代码:
条件5:执行Array.prototype.concat函数,走进前面4个条件的代码执行流程。
代码:
为了方便演示,直接将结果打印出来
2.1.2.3结果演示:
2.1.2.3
2.1.2.3
这里可以看到,在hole元素A[1]之后,由于触发了上述所说的越界读漏洞,读取了奇怪的数字代替了原本数组的元素。
2.1.3 补丁修复
这里的修复的手段是
ß---------------------------插入了补丁
该补丁是在
switch(array->GetElementsKind)开始前就插入了HasOnlySimpleElements(isolate, *receiver)的检测,检查是否存在Element元素的Getter,Setter回调。
如果有,就执行IteratesSlow(isolate, receiver, length, visitor),不会进入FOR_WITH_HANDLE_SCOPE的循环过程。
但是FOR_WITH_HANDLE_SCOPE循环过程实际上还存在,如果在FOR_WITH_HANDLE_SCOPE循环里面还能发现有别的办法执行自定义的JS,依旧可以利用自定义的JS来进行数组length减小,垃圾回收的操作,使用同样的办法来触发越界读取漏洞。
结合之前的分析结果,可以将触发这一类漏洞的问题就可以分解为两个部分:
1)我们可以通过某种手段,绕过HasOnlySimpleElements(isolate, *receiver)的检测,进入FOR_WITH_HANDLE_SCOP循环。
2)在FOR_WITH_HANDLE_SCOP循环返回之前,控制执行自定义的JS代码。
2.2:CVE-2017-5030
2.2.1在CVE-2016-1646修补之后的几个月, Symbol.species和代理对象Proxy object被引入
2.2.1.1:Symbol.species对于Array.prototype.concat的影响:
Symbol.species操作会重写对象的构造函数,在Array.prototype.concat这样的JavaScript内置函数中,会使用Symbol.species里面的函数重新加载执行构造函数,来创建新的对象,从而能影响到Array.prototype.concat的返回结果。
这里有简单的演示案例:
图 2.1.1.1
图 2.1.1.1简单验证了Symbol.species是可以影响Array.prototype.concat的返回结果,这里将一个Number 5写入了的返回结果。
但是问题是Symbol.species,在哪里,如何影响到Array.prototype.concat的返回结果。
2.2.1.2 Array.prototype.concat实现过程对于Symbol.species的处理
图 2.2.1.2
实际上v8的处理,是在Array.prototype.concat执行之中,为Symbol.species新建一个Handle<Object> species对象,然后在执行Handle<Object> species对象的JS代码一次,然后将其结果放入visitor之中,从前面2.1中的介绍可以知道,visitor是处理返回的对象。
也就是说图 2.2.1.2的代码片段说明,我们通过Handle<Object> species对象的执行,可以在Array.prototype.concat返回visitor之中写入一个我们自己控制的对象。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2022-9-2 19:58
被苏啊树编辑
,原因: