ThinkPHP5.0.x命令执行分析

漏洞介绍

18年末TP5爆出了这么个命令执行的漏洞,但是前段时间事情确实是多,没有时间来分析,现在来分析一下。先给出完整POC:POST_method=__construct&filter=system&method=get&server[REQUEST_METHOD]=whoamitohttp://localhost/thinkphp_5.0.22_with_extend/public/?s=captchaenter description here我分析和复现使用的版本是thinkphp5.0.22

漏洞分析

正向分析

按照习惯,我直接把断点定在了漏洞触发的地方:thinkphp/library/think/Route.php第1083行enter description here调用栈如图enter description here先正向分析一下,程序入口是index.phpenter description here然后进入start.phpenter description here再跟进run函数enter description here可以看到,在进入$data = self::exec($dispatch, $config);之前,$dispatch的值是通过$dispatch = self::routeCheck($request, $config);进行赋值的,此时如果开启了debug模式,就会调用$request->param,在接下来的exec函数也同样是调用了这个,所以这个漏洞开不开启debug都可以触发。跟进execenter description here跟入Request::instance()->param()enter description here进入$this->method()enter description here进入server()enter description here跟入input()enter description hereinput()中可以看到,传入$this->filterValue()$filter$this->getFilter()的返回值,这里先不管他,直接到最后触发命令执行的filterValue()看看enter description here这里有一个foreach,然后通过call_user_func执行任意命令

构造可控参数

那么,我们需要控制的是filter$value这两个变量,根据函数的调用栈,先回到一开始的run函数enter description here传入exec()$dispatchrouteCheck()的返回值,我们跟进routeCheck()enter description here然后跟进check()enter description here在这个函数中,$rules决定了返回值,而$rules的值由$method决定,$method的值则由$request->method()决定。事实上这里才是第一次进入method()的地方enter description here也是整个漏洞我认为最关键的一个地方。首先它把strtoupper($_POST[Config::get('var_method')])赋给$this->method,这里我们先看看Config::get('var_method')是什么,首先知道,这个东西是拿来获取配置参数的enter description here然后我们看看配置文件里的这个参数enter description here所以,只需要POST_method=???,我们就可以完全控制这个method的值,从而进入动态函数执行this->{$this->method}($_POST),然后是一个很关键的点,那就是,Request类的构造函数存在一个参数的遍历赋值,那么理论上,我们通过post_method=__construct,然后再进行相应的变量覆盖,就可以实现任意命令的执行。

POC的构造

接下来看一些细节上的东西,以及如何成功构造POC。
$filter直接变量覆盖即可,传入filter=systementer description here接下来再看$value如何构造,我们可以看到在input()中,最后传入filterValue()$data的值是通过$data[$val]赋的enter description here而这里的$data实际上就是可以变量覆盖的server,所以,这里我们通过覆盖server['REQUEST_METHOD']的值,就可以实现另一个命令执行的参数的覆盖。然后再看,为什么POC中要有s=captcha?在vendor/topthink/think-captcha/src/help.php中,可以看到路由信息enter description here然后在Route.php中,可以看到这么一段enter description here所以type=methodenter description here正是因为这样,我们在exec()中才能进入case 'method'.最后,为什么要有method=get?在check()中,有一个对$rules的赋值,这里我们需要通过使method=get,才能正确获取路由信息,从而通过routeCheck()enter description here

总结

本质上就是一个因为参数检查不完整导致的变量覆盖漏洞,漏洞的发现者牛逼。