漏洞介绍
18年末TP5爆出了这么个命令执行的漏洞,但是前段时间事情确实是多,没有时间来分析,现在来分析一下。先给出完整POC:POST_method=__construct&filter=system&method=get&server[REQUEST_METHOD]=whoami
tohttp://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()
总结
本质上就是一个因为参数检查不完整导致的变量覆盖漏洞,漏洞的发现者牛逼。