ThinkPHP5.1.x反序列化调用链复现分析

2019-12-20 14:37:276103人阅读

1.    前言

最近研究了phpok5.3反序列化可直接getshell的漏洞,又见到土司里对ThinkPHP多个版本调用链的总结,于是便想着来复现分析一下。本文只是对反序列化调用链的分析,POC并不能直接被利用,需要基于ThinkPHP开发的代码有可用的反序列化入口。

2.   反序列化调用链分析

通常PHP反序列化的漏洞的入口为类的魔术方法,PHP是这样定义魔术方法的:PHP将所有以__(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以__ 为前缀。那么这里可以理解魔术方法是PHP为类保留的有独特功能的内置方法。而PHP反序列化最常见的入口方法为析构方法__destruct。

__destruct: PHP5引入了析构方法的概念,这类似于其它面向对象的语言,如C++,析构方法会在当某个对象的所有引用都被删除或者当对象被显式销毁时执行。实际上,当PHP脚本运行结束之前,所有的变量都会被销毁,因此析构方法在类被反序列化并实例化后必然会被调用。

此处也会以__destruct作为入口,文件位于/thinkphp/library/think/process/ pipes/Windows.php,__destruct调用了Windows类的removeFiles方法。

1.png

 $this->files为类属性,在反序列化时可控,因此$filename可控,163行进入file_exists方法,如果$filename为文件路径此处可导致任意文件删除,但反序列化通常是为了寻求RCE。

2.png

此处就要介绍另外一个魔术方法__toString:当一个对象被当作字符串对待的时候,会触发这个魔术方法。而当file_exists处理时,$filename被当做了字符串,因此如果$filename为类对象的话,此处可以触发该类的__toString方法。

3.png

跟进到thinkphp/library/think/model/concern/Conversion.php的__toString方法,这里调用了toJson方法。

3.png

接着调用了toArray方法,主要是将该对象转成JSON字符串,跟进到toArray方法的183-195行。此时$this->append为类属性可控,则$key、$name可控,我们需要寻找到类似$可控对象->方法($可控参数)的调用链,因此toArray方法需要到193行$relation->visible($name)。

4.png

为了满足这个条件,先跟进到getRelation方法,位于/thinkphp/library/think/ model/concern/RelationShip.php。可让$name不为空进入elseif,让$this->relation默认为空,而$name肯定不存在于$this->relation的键值中,因此getRelation方法返回为空。

5.png

接着会调用toArray方法191行的getAttr方法,方法位于/thinkphp/library/think/model/concern/Attribute.php的472行,476行调用了getData方法。


跟进getData方法,267行、271行条件根据上面的分析不能满足,因此只能进入269行分支,$this->data可控并且$name为其键值。

7.png

综上分析,toArray方法193行的$relation可控为$this->data[$key],$name为$this->append[$key]。但认真看Conversion、Attribute类,发现定义为trait:trait是一种为类似PHP 的单继承语言而准备的代码复用机制。

8.png


因此我们需要找一个同时引入Conversion、Attribute类的方法,通过搜索发现了/thinkphp/library/think/Model.php类,但Model定义为abstract,抽象类无法被实例化。

9.png


因此需要继续寻找一个继承Model类的类文件,这里搜索到了/thinkphp/ library/think/model/Pivot.php,因此可以实例化Pivot类完成下述调用链:

file_exists(new Pivot)->Model->Conversion、Attribute

->_toString->toJson->toArray->getAttr->$relation->visible($name)

10.png

满足了$可控对象->方法($可控参数)的调用链,我们只能调用某个类的visible方法,并不能够满足RCE条件;此时,需要再介绍一个魔术方法__call: 

当调用一个不可访问的方法(如未定义,或者不可见时), __call()就会被调用。因此,需要寻找一个既包含__call方法而又不含有visible方法的类。经过搜索发现think/library/think/Request.php满足上述条件,$method参数为不存在的方法名即visible,而$this->hook为类属性可控,因此可以进入第一个if分支。

11.png

但330行调用了array_unshift会向$args数组开头插入Request类对象,导致call_user_function_array没办法顺利的执行任意命令,但此处可以调用任意方法,这里根据之前ThinkPHP命令执行的经验,filterValue方法存在call_user_func方法,有RCE的可能,但若是直接调用,$value参数传递的值为$this即Request对象,没办法控制调用方法的参数。

12.png

继续寻找哪些方法调用了filterValue,发现input方法调用了filterValue,但此时$data为$this依然不可控。

13.png

继续向上溯源发现param方法调用了input方法,953行此时$this->param可由$this->get即$_GET传递,因此可控,但$name仍然为对象不可控。

14.png

继续向上追溯,我们发现了isAjax方法,$this->config为类属性完全可控。

15.png

因此可以采用如下的调用链条:

_call()->isAjax()->param()->input()->filterValue()

因为只是反序列化调用链条,因此复现时需要写一个反序列化入口到application/index/controller/Index.php中。

16.png

$input参数值可用下面的POC产生

17.png

复现如下:

18.png

3.   参考链接

https://xz.aliyun.com/t/6619

 

 


本文由百度安全原创,若需转载,请注明出处及原文链接 

 


0
现金券
0
兑换券
立即领取
领取成功