挖洞的过程往往伴随着不断踩坑

  • A+
所属分类:安全文章

在xz社区上看到某CMS中存在反序列化触发点,可以触发ThinkPHP 5反序列任意文件删除的利用链。很早以前分析过一次低版本的反序列化RCE的利用链,但在这5.0.24版本中发现没有利用成功,于是就再次尝试和分析挖掘。



原有的利用链无法利用的原因



在早之前的利用链是利用Request.php的__call方法进行RCE操作,其中$hook变量是该类能够成功利用的原因之一。但是在5.0.24中$hook变量被设置为了静态变量,也就是不可控了。这直接导致了Request中的__call函数无法执行任意的函数。

挖洞的过程往往伴随着不断踩坑

挖洞的过程往往伴随着不断踩坑

因此,无法利用Reuqest的__call了。不过从在到达__call方法之前的利用链还在,只需要找到新的能够RCE的点即可。



寻找替代品



由于Reuqest的__call已经无效,那么就需要寻找其他的类的__call作为执行RCE的跳板。全局搜索

挖洞的过程往往伴随着不断踩坑

在众多个类中进行排查,可以找到Output.php是比较合适的(比较合适是后来分析出来的)。

挖洞的过程往往伴随着不断踩坑

如果进入执行output的__call可以执行output的block,因为$this->style是可控的。

挖洞的过程往往伴随着不断踩坑


挖洞的过程往往伴随着不断踩坑

挖洞的过程往往伴随着不断踩坑

block执行会到达writeln然后再到达write,在此因为handler可控,所以可以执行任意类的write函数。全局搜索有write方法的类,可以找到Memcache类,会发现可以继续执行任意类的set方法。

挖洞的过程往往伴随着不断踩坑

继续全局搜索,找到了File类的set方法,可以看到在163行出有进行文件写入的操作。

挖洞的过程往往伴随着不断踩坑


但在,实际分析的过程中会发现163行处,不是真正写入shell的地方,而166行才是。原因在于162行处的$data变量也就是传入的$vuale没法控制,但在149行处我们可以控制文件名。

挖洞的过程往往伴随着不断踩坑

如上代码,可以通过控制options数组对文件名控制。然后继续跟进setTagItem方法

挖洞的过程往往伴随着不断踩坑

会发现在202行处,会以传入的$name作为写入文件的内容进行写入,然后在204行再次执行File类的set方法进行文件写入,此时的文件的内容就是可控了。

挖洞的过程往往伴随着不断踩坑



Model类跳板



已经寻找到了能写入shell的类,现在转而分析Model类。

挖洞的过程往往伴随着不断踩坑

在5.0.24中,反序列化链仍然能到达这个toArray方法。在5.0.1中,使用的895行处的代码来触发Request的类,但在本条链中并不使用,原因在895行的$name会是一个数组,而后续的利用要求这个变量是一个字符串。因此,需要将流程控制到916行处,此处的$attr变量是一个字符串,满足后续利用的需求。


为了将流程控制到916行,最重要的是需要满足904行和906行。并且在906行处需要得到一个Output对象。跟进getRelationData函数。

挖洞的过程往往伴随着不断踩坑

会发现这个方法需要传入一个Relation的对象,并且需要满足如下代码

 if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) { $value = $this->parent;## 1.$this->parent需要有值## 2.$modelRelation需要有isSelRelation这个方法## 3.$modelRelation需要有get_class方法,并且返回值的类要和$this->parent的类相同

滿足上述条件,就可以通过$this->parent来控制返回一個OutPut对象。其中,Relation是一个抽象类,且已经对getModel方法进行定义

挖洞的过程往往伴随着不断踩坑

全局搜索继承Relation的子类

挖洞的过程往往伴随着不断踩坑

找到的是OnToOne这个抽象类,继续寻找实现这个类的子类,可以找到如下的HasOne类。

挖洞的过程往往伴随着不断踩坑

HasOne的对象可以执行从Relation继承来的getModel方法,并且执行任意一个类的getModel方法,全局搜索带有getModel方法的类。

挖洞的过程往往伴随着不断踩坑

此处选用Query类

挖洞的过程往往伴随着不断踩坑

这样就可以控制$modelRelation->getModel()的返回值为一个OutPut对象,条件即可成立。

挖洞的过程往往伴随着不断踩坑

流程继续向下走,此时$value已经是一个Output对象了,再看912行,如果需要调用Output的__call方法进行RCE,那么一定要能够控制传入的参数。显然,参数来源于数组$bindAttr,getBindAttr方法的返回值。值得一提的是,此处的$modelRelation是hasOne对象,而不是Output对象。该方法是hasOne从OneToOne抽象类继承来的,如下,显然可控。

挖洞的过程往往伴随着不断踩坑

至此,整条利用链已经构成

利用效果如下:

挖洞的过程往往伴随着不断踩坑



遇到的一些情况和尝试解决的过程



实际的分析过程远远没有一篇文章上看上去的那么顺利,在调试的过程中遇到了一些问题。为了执行代码,需要绕过exit问题,显然可以想到使用伪协议的方式进行编码绕过,使用rot13编码是极为方便的。但是也带来了一个问题,由于文件内容是由于文件名控制,这样的结果是导致了文件名引入了特殊符号在windows环境下无法写入成功。于是我尝试使用base64的方式写入,写入的效果如下:

挖洞的过程往往伴随着不断踩坑

注:整个流程会写入两次缓冲文件,上图是第一次写入,可以看到exit是被编码了。观察上述的文件名,不难发现a是我用来填充字符串个数的,P开始才是正常的base64编码的值,解密出来是 <?php phpinfo();#abcd

挖洞的过程往往伴随着不断踩坑

为了避免出现特殊字符(=)采用注释的方式,拼凑字符串,找到一个加密以后不会出现等号的值。

挖洞的过程往往伴随着不断踩坑

如上代码,还要求了一个问题,就是base64加密出来的字符串不能带有。如果出现,会出现多一层目录,而且再复现的时候,发现多一层目录往往会写入失败。

挖洞的过程往往伴随着不断踩坑

那么需要填充几个垃圾字符a来促使解码成功呢?我计算很久,但没算到合适的值,于是采用爆破!写了一小段代码,来暴力计算一下,其实也才20次的循环。理论上8次以内肯定能出结果。

挖洞的过程往往伴随着不断踩坑


根据爆破的结果来看,我之前的计算结果是没有问题的。于是高高兴兴拿POC去验证。

挖洞的过程往往伴随着不断踩坑

然而,诡异的事情还是发生了,文件内容没有写入成功。

冷静回顾了一下整个流程,发现并没有什么问题,那么问题还是只能出现在加密上,可以确定的是填充的字符串个数是没问题的。那么就要考虑是不是有特殊符号而导致的。

    观察这串需要被解密的字符串

php://filter/write=convert.base64-decode/resource=./runtime/cache/aaaaaaaaPD9waHAgcGhwaW5mbygpOyNhYmNk

抛去.-:这些不会参与加密解密的,只剩下/=。等号在base64是具有特殊意义的,于是我对它产生了怀疑,写了一小段代码验证一下。

挖洞的过程往往伴随着不断踩坑

当我把等号替换了就发现可以写入了。这可能是伪协议的解密方式和base64_decode有差别之处吧,但是等号又是无可避免的,所以以这种方式的利用失败了。




总结



整个反序列化的过程分析下来花了点时间,难度还算是可以接受。更多时间是花在了解决遇到的问题之上。文章写的很潦草粗糙,单看此篇可能不知道这篇是在讲些啥。没有调试技巧,没有骚姿势,如果上手调试,也许会遇到一些问题,那么这篇文章可能就是有一定帮助。

相关文章

原创 为 web 安全初学者准备的新春礼物

挖洞的过程往往伴随着不断踩坑

本文始发于微信公众号(信安之路):挖洞的过程往往伴随着不断踩坑

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: