ThinkPHP5.1.X反序列化利用链

2019-10-06 约 224 字 预计阅读 2 分钟

声明:本文 【ThinkPHP5.1.X反序列化利用链】 由作者 mochazz 于 2019-10-06 08:10:57 首发 先知社区 曾经 浏览数 644 次

感谢 mochazz 的辛苦付出!

2019年7月25日,在 ThinkPHP 官方 github 上有人提交了这个 issue ,遂想一探究竟。(鉴于该POP链已大范围公开,这里便公开之前写的分析文章)

环境搭建

➜  composer create-project --prefer-dist topthink/think tp5137
➜  cd tp5137
➜  vim composer.json # 把"topthink/framework": "5.1.*"改成"topthink/framework": "5.1.37"
➜  composer update

application/index/controller/Index.php 代码修改成如下:

<?php
namespace app\index\controller;

class Index
{
    public function index()
    {
        $u = unserialize($_GET['c']);
        return 'hhh';
    }

    public function hello($name = 'ThinkPHP5')
    {
        return 'hello,' . $name;
    }
}

利用条件

  • 有一个内容完全可控的反序列化点,例如: unserialize(可控变量)

  • 存在文件上传、文件名完全可控、使用了文件操作函数,例如: file_exists('phar://恶意文件')

(满足以上任意一个条件即可)

漏洞链

这个漏洞个人认为比较有意思的是:通过 file_exists 函数触发类的 __toString 方法。下面,我们具体分析一下整个漏洞攻击链。

think\process\pipes\Windows 类的 __destruct 方法中,存在一个删除文件功能,而这里的文件名 $filename 变量是可控。如果我们将一个类赋值给 $filename 变量,那么在 file_exists($filename) 的时候,就会触发这个类的 __toString 方法。因为 file_exists 函数需要的是一个字符串类型的参数,如果传入一个对象,就会先调用该类 __toString 方法,将其转换成字符串,然后再判断。

接下来,我们就来寻找可利用的 __toString 方法。全局搜索到的 __toString 方法其实不多,这里有两处都可以利用。它们的区别在于利用 think\Collection 构造的链要多构造一步,我们这里只分析链较短的 think\model\concern\Conversion

如下图 第191-192行 所示,$relation 变量来自 $this->data[$name] ,而这个变量是可以控制的。第192行$name 变量来自 $this->append ,也是可以控制的。所以 $relation->visible($name) 就变成了:可控类->visible(可控变量) 。那么接下来,就要找可利用的 visible 方法,或者没有 visible 方法,但有可利用的 __call 方法。

全局搜了一下 visible 方法大概有3处,但是都不能利用,所以我们考虑寻找可利用的 __call 方法。在搜 __call 方法的时候,会发现有一处 think\Request 类比较好利用,因为这里 call_user_func_array 函数的第一个参数完全可控。构造 EXP 的时候可以传入数组,变成 call_user_func_array(array(任意类,任意方法),$args) ,这样我们就可以调用任意类的任意方法了。虽然第330行用 array_unshift 函数把本类对象 $this 放在数组变量 $args 的第一个,但是我们可以寻找不受这个参数影响的方法。

分析过 ThinkPHP 历史 RCE 漏洞的人可能知道, think\Request 类的 input 方法经常是链中一个非常棒的 Gadget ,相当于 call_user_func($filter,$data) 。但是前面我们说过, $args 数组变量的第一个元素,是一个固定死的类对象,所以这里我们不能直接调用 input 方法,而应该寻找调用 input 的方法。

调用 input 的方法共有7处,我这里直接选择比较简单的 request 方法来分析,因为这7处关键代码都类似。如果这里通过调用 request 方法间接调用 input 方法,实际上框架会报错退出的。因为这里传给 input 方法的 $name (下图右边第1092行),实际上是先前 call_user_func_array(array(任意类,任意方法),$args)$args 数组的第一个变量,即我们前面说的一个固定死的类对象。然而如果把一个类对象作为 $data 传给 input 方法,那么在强转成字符串的时候(上图左边1354行),框架就会报错退出。

所以我们这里还要继续找有哪些地方调用了这7处。这里搜了调用 param 方法的地方,发现 isAjaxisPjax 都可以利用,因为他们传入 param 方法的第一个参数均可控。

这样,整个漏洞链就构造完了。下面举个例子,比如我们想执行 system('id') 代码,那么我们只要让传入的 Request 对象的 $this->filter='system'$this->param=array('id') 即可。最终 EXP 如下(不同版本EXP不一样):

  • 5.1.16<=ThinkPHP版本<=5.1.37
  • 5.1.14<=ThinkPHP版本<=5.1.15
  • 5.1.3<=ThinkPHP版本<=5.1.13

PS:为了避免不必要的麻烦,已将原文中的 EXP 部分删除。

最后整理一下攻击链的流程图:

参考

挖掘暗藏thinkphp中的反序列利用链

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now