首页
社区
课程
招聘
[原创] V8 Array.prototype.concat函数出现过的issues和他们的POC们
发表于: 2022-9-2 19:38 16539

[原创] V8 Array.prototype.concat函数出现过的issues和他们的POC们

2022-9-2 19:38
16539

 

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 被苏啊树编辑 ,原因:
收藏
免费 3
支持
分享
打赏 + 100.00雪花
打赏次数 1 雪花 + 100.00
 
赞赏  Editor   +100.00 2022/09/07 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (1)
雪    币: 5317
活跃值: (3318)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
2

编辑图片好幸苦

2022-9-2 19:39
0
游客
登录 | 注册 方可回帖
返回
//