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

 

前言

前几天审计某cms基于ThinkPHP5.0.24开发,反序列化没有可以较好的利用链,这里分享下挖掘ThinkPHP5.0.24反序列化利用链过程.该POP实现任意文件内容写入,达到getshell的目的

 

环境搭建

Debian

apache2+mysql+ThinkPHP5.0.24+php5.6

下载地址:http://www.thinkphp.cn/donate/download/id/1279.html

文件:application/index/controller/Index.php

<?php
namespace appindexcontroller;


class Index
{
    public function index($input='')
    {
        echo "Welcome thinkphp 5.0.24";
        echo $input;
        unserialize($input);
    }
}

访问:http://127.0.0.1/cms/tp50/public/index.php

 

简述

Thinkphp 5.0.x反序列化最后触发RCE,要调用的Request__call方法.

但是由于这里self::$hook[$method]不可控,无法成功利用

我的思路是在找其他的__call,其他魔术方法搜了一圈没有可以进一步利用.

文件:thinkphp/library/think/console/Output.php

最后选择Output类中的__call方法,这里调用block方法.后续可以当做跳板

 

POP链分析

从头开始分析

反序列化起点:thinkphp/library/think/process/pipes/Windows.php removeFiles方法

跟进removeFiles方法

跳板:file_exists方法能够触发__toString魔术方法

跳板利用点:thinkphp/library/think/Model.php

Model抽象类__toString

跟进toJson方法至toArray方法

如下图Model抽象类toArray方法,存在三个地方可以执行__call

但是我们目的是调用Output类__call且能够继续利用,调试后选择第三处当做调板

 $item[$key] = $value ? $value->getAttr($attr) : null;

分析下如何达到该行代码

 $item[$key] = $value ? $value->getAttr($attr) : null;

这里直接看else分支

溯源$values变量,比较关键是下面两行

$modelRelation = $this->$relation();
$value         = $this->getRelationData($modelRelation);

$modelRelation值可以利用Model类中的getError方法

跟进getRelationData方法,这里最后传入的$modelRelation需要Relation类型

最后返回值$values需要经过if语句判断

$this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)

全局搜索下,可以利用HasOne类

最后$attr值,由$bindAttr = $modelRelation->getBindAttr();执行后的结果.

跟进OneToOne抽象类getBindAttr方法,binAttr类变量可控.

至此代码执行到$item[$key] = $value ? $value->getAttr($attr) : null;就能够执行Output类__call魔术方法

跟进Output类block方法

继续跟进writelin方法,最后会调用write方法

这里$this->handle可控,全局搜索write方法,进一步利用

定位到:thinkphp/library/think/session/driver/Memcached.php

类: Memcached

继续搜索可用set方法

定位到:thinkphp/library/think/cache/driver/File.php

类:File

最后可以直接执行file_put_contents方法写入shell

$filename可控且可以利用伪协议绕过exit

$data值比较棘手,这里有个坑,由于最后调用set方法中的参数来自先前调用的write方法

只能为true,且这里$expire只能为数值,这样文件内容就无法写shell

继续执行,跟进下方的setTagItem方法

会再执行一次set方法,且这里文件内容$value通过$name赋值(文件名)

所以可以在文件名上做手脚

示例:php://filter/write=string.rot13/resource=./<?cuc cucvasb();?>

 

POP链(图)

 

EXP

马赛克

 

复现

写入文件

实战是需要找个可写目录

读取文件

 

结语

感谢@水泡泡师傅解答问题

整条POP分析下来挺有趣,希望师傅们喜欢.

(完)