漏洞环境

漏洞测试环境:PHP5.6+Linux+ThinkPHP5.0.24

漏洞测试代码 application/index/controller/Index.php

<?php
namespace app\index\controller;

class Index
{
    public function index()
    {
        $c = unserialize($_GET['c']);
        var_dump($c);
        return 'Welcome to thinkphp5.0.24';
    }
}

漏洞分析

POP 链入口点为 think\process\pipes:__destruct 方法。通过 __destruct 方法里调用的 removeFiles 方法,可以利用 file_exists 函数来触发任意类的 __toString 方法,这里我们选择 think\Model 类来触发。由于该类为抽象类,所以我们后续在构造 EXP 的时候得使用其子类,例如: think\Model\Pivot 类。

在先前的 ThinkPHP5.1.X 反序列化链中,都是在调用 think\Model:toArray() 时触发 think\Request:__call() 。然而 ThinkPHP5.0.X 中的 think\Request:__call() 写法与 ThinkPHP5.1.X 的不一样了,因为成员变量不可控。

所以我们需要找其他可利用的 __call 方法,这里我们选择 think\console\Output 类。其 __call 方法最终会调用到 $this->handle->write() ,而这个 $this->handle 可控,所以思路就是找一个类的 write 方法可以实现写文件。

这里,我们将 think\console\Output 类的 handler 属性设置成 think\session\driver\Memcached 类对象。因为我们发现 think\cache\driver\File:set() 方法可以写文件,刚好 think\session\driver\Memcached:write() 中调用了 set 方法,且又有一个可控的 handler 属性。我们继续深入,看看如何利用 think\cache\driver\File:set() 方法写文件。

首先, $filename 的前一部分 $this->options['path'] 可控,但是这时的 $value=true ,其在 think\console\Output:writeln() 方法中固定为 true ,也就是说下图第158行写入文件的代码我们不可控。但是我们继续往下看,发现 $this->setTagItem($filename) 中又调用了 set 方法写文件,而此时传入的 $value 为我们部分可控的 $filename 。那么此时,我们就可以使用 PHP 的伪协议来写 shell

例如,我们将部分可控的 $this->options['path'] 设置成 php://filter/write=string.rot13/resource=<?cuc @riny($_TRG[_]);?> ,最后就会生成一个一句话木马(访问 webshell 的时候,注意要将文件名中的 ?URL编码%3f )。

后面的利用基本讲完了,我们再回到刚刚的 think\Model:toArray() ,因为我们还没说明选择何处调用 __call 方法。这里我们选择通过下图第912行的 $value->getAttr() ,来触发 think\console\Output:__call() 方法。

那么这里的 $value 一定是 think\console\Output 类对象,其在上图第902行 $this->getRelationData($modelRelation) 被赋值。其中,参数 $modelRelation = $this->$relation() ,实际上就是 think\Model 类任意方法的返回结果。这里我选择返回结果简单可控的 getError 方法。

参数已经可控了,接下来我们就直接看 think\Model:getRelationData() 的具体代码。下图的 $this->parent 肯定是 think\console\Output 类对象,所以我们只需要满足下面的 if 语句即可。这里我们 think\model\Relation 类的 isSelfRelation、getModel 方法返回值都可控,所以我们找 think\model\Relation 的子类套一下即可。

万一网站不允许写文件,我们可以稍微修改下 payload 用于创建一个 0755 权限的目录(这里利用的是 think\cache\driver\File:getCacheKey() 中的 mkdir 函数),然后再往这个目录写文件。

最终生成 shellEXP 如下:

已删除

参考

ThinkPHP v5.0.x 反序列化利用链挖掘

点击收藏 | 2 关注 | 3
  • 动动手指,沙发就是你的了!
登录 后跟帖