ThinkPHP 5.0.0~5.0.23 RCE 漏洞复现分析

ThinkPHP 5.0.0~5.0.23 RCE 漏洞复现分析

我印象很深刻,在19年年初的时候接连爆发了thinkphp两个RCE漏洞,一个是该漏洞,一个是5.x全版本的。当时各个群里面都讨论的很火热,一堆人拿着poc在网上批量的扫,本身这个RCE利用难度不高payload也简单,而且tp站用的人也很多,所以打算先分析一下该漏洞原理。

先用网上的payload进行测试一下,我这里是用的vulhub的漏洞环境。

POST /index.php?s=captcha HTTP/1.1

_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=id

但是并没有执行成功,无论怎样payload都没有得到回显,本以为是漏洞环境出了问题,于是在本地进行了测试。
本地测试回显了,看来是被阿里云拦截了。所以还是直接根据网上的文章结合payload审计代码。

先分析这个流程,tp程序从 App.php文件开始。

会根据请求的URL调用routeCheck进行调度解析获得到$dispatch。

然后进入exec($dispatch, $config)根据$dispatch类型的不同来进行处理。payload中访问的url为index.php?s=captcha。在vendor/topthink/think-captcha/src/helper.php中captcha注册了路由。

所以它对应的dispatch为method,通过调用Request类中的method方法来获取当前的http请求类型,该函数的实现在 thinkphp/library/think/Request.php。

因此通过POST一个_method参数,即可进入判断,并执行$this->{$this->method}($_POST)语句。因此通过指定_method即可完成对该类的任意方法的调用,其传入对应的参数即对应的$_POST数组。Request类的构造函数__construct代码如下。

利用foreach循环,和POST传入数组即可对Request对象的成员属性进行覆盖。其中$this->filter保存着全局过滤规则。经过覆盖,相关变量变为:

$this
    method = "get"
    get = {array} [0]
        0 = dir
    filter =  {array} [0]
        0 = system

注意我们请求的路由是?s=captcha,它对应的注册规则为\think\Route::get。在method方法结束后,返回的$this->method值应为get这样才能不出错,所以payload中有个method=get。在进行完路由检测后,执行self::exec($dispatch, $config),在thinkphp/library/think/App.php:445,由于$dispatch值为method,将会进入如下分支。

跟入Request::instance()->param(),该方法用于处理请求中的各种参数。

该方法中$this->param通过array_merge将当前请求参数和URL地址中的参数合并。回忆一下前面已经通过__construct设置了$this->get为dir。继续跟入$this->input。

该方法用于对请求中的数据即接收到的参数进行过滤,而过滤器通过$this->getFilter获得。

前面$this->filter已经被设置为system,所以getFilter返回后$filter值。

回到input函数,由于$data是前面传入的$this->param即数组,所以接着会调用array_walk_recursive($data, [$this, ‘filterValue’], $filter),对$data中的每一个值调用filterValue函数,最终调用了call_user_func执行代码。

zgao

愿有一日,安全圈的师傅们都能用上Zgao写的工具。