漏洞介绍
18年末TP5爆出了这么个命令执行的漏洞,但是前段时间事情确实是多,没有时间来分析,现在来分析一下。先给出完整POC:POST_method=__construct&filter=system&method=get&server[REQUEST_METHOD]=whoamitohttp://localhost/thinkphp_5.0.22_with_extend/public/?s=captcha
我分析和复现使用的版本是thinkphp5.0.22
漏洞分析
正向分析
按照习惯,我直接把断点定在了漏洞触发的地方:thinkphp/library/think/Route.php第1083行
调用栈如图
先正向分析一下,程序入口是index.php
然后进入start.php
再跟进run函数
可以看到,在进入$data = self::exec($dispatch, $config);之前,$dispatch的值是通过$dispatch = self::routeCheck($request, $config);进行赋值的,此时如果开启了debug模式,就会调用$request->param,在接下来的exec函数也同样是调用了这个,所以这个漏洞开不开启debug都可以触发。跟进exec
跟入Request::instance()->param()
进入$this->method()
进入server()
跟入input()
从input()中可以看到,传入$this->filterValue()的$filter是$this->getFilter()的返回值,这里先不管他,直接到最后触发命令执行的filterValue()看看
这里有一个foreach,然后通过call_user_func执行任意命令
构造可控参数
那么,我们需要控制的是filter和$value这两个变量,根据函数的调用栈,先回到一开始的run函数
传入exec()的$dispatch是routeCheck()的返回值,我们跟进routeCheck()
然后跟进check()
在这个函数中,$rules决定了返回值,而$rules的值由$method决定,$method的值则由$request->method()决定。事实上这里才是第一次进入method()的地方
也是整个漏洞我认为最关键的一个地方。首先它把strtoupper($_POST[Config::get('var_method')])赋给$this->method,这里我们先看看Config::get('var_method')是什么,首先知道,这个东西是拿来获取配置参数的
然后我们看看配置文件里的这个参数
所以,只需要POST_method=???,我们就可以完全控制这个method的值,从而进入动态函数执行this->{$this->method}($_POST),然后是一个很关键的点,那就是,Request类的构造函数存在一个参数的遍历赋值,那么理论上,我们通过post_method=__construct,然后再进行相应的变量覆盖,就可以实现任意命令的执行。
POC的构造
接下来看一些细节上的东西,以及如何成功构造POC。$filter直接变量覆盖即可,传入filter=system
接下来再看$value如何构造,我们可以看到在input()中,最后传入filterValue()的$data的值是通过$data[$val]赋的
而这里的$data实际上就是可以变量覆盖的server,所以,这里我们通过覆盖server['REQUEST_METHOD']的值,就可以实现另一个命令执行的参数的覆盖。然后再看,为什么POC中要有s=captcha?在vendor/topthink/think-captcha/src/help.php中,可以看到路由信息
然后在Route.php中,可以看到这么一段
所以type=method
正是因为这样,我们在exec()中才能进入case 'method'.最后,为什么要有method=get?在check()中,有一个对$rules的赋值,这里我们需要通过使method=get,才能正确获取路由信息,从而通过routeCheck()
总结
本质上就是一个因为参数检查不完整导致的变量覆盖漏洞,漏洞的发现者牛逼。