漏洞存在于 ThinkPHP 底层没有对控制器名进行很好的合法性校验,导致在未开启强制路由的情况下,用户可以调用任意类的任意方法,最终导致 远程代码执行漏洞 的产生。
环境搭建:Phpstudy:
- OS:Windows
- PHP:7.3.4
- ThinkPHP:5.0.22
http://xxxxxx.xxx/public/?s=captcha Body _method=__construct&filter[]=system&method=get&get[]=whoami _method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=whoami复现分析 POC 1
调试分析,Thinkphp的程序是从App.php开始的,在App::run中,先通过$dispatch = self::routeCheck($request, $config);对路由进行检测
在该方法中,又调用了Route::check方法,跟进
存在$method = strtolower($request->method());,跟进method方法,进入了Request.php
默认$method = false时进入分支条件
注意到:$this->method = strtoupper($_POST[Config::get('var_method')]);
这里的var_method对应_method,在Config.php中
$method来自可控的$_POST数组,而且在获取之后没有进行任何检查,直接把它作为 Request 类的方法进行调用,同时,该方法传入的参数是可控数据$_POST。也就相当于可以随意调用 Request 类的部分方法。
我们传入的参数:_method=__construct&filter[]=system&method=get&get[]=whoami
它会调用__construct方法,而该方法中有类属性覆盖功能
那么在结束method方法时,我们需要返回参数$this->method为get
继续分析
进入了exec方法,?s=captcha就可以让$dispatch['type']是method
在 ThinkPHP5 完整版中,定义了验证码类的路由地址。程序在初始化时,会通过自动类加载机制,将 vendor 目录下的文件加载,这样在 GET 方式中便多了这一条路由。我们便可以利用这一路由地址,使得$dispatch['type']等于 method ,从而完成 远程代码执行 漏洞。
进入Request::instance()->param()
$this->param通过array_merge将当前请求参数和URL地址中的参数合并。
跟进get方法
由于之前的变量覆盖get有值
所以会进入input方法
它返回了get数组值,而$this->param也会有值
接着我们再次进入input方法
进入解析过滤器
发现是通过$filter参数获取的方法,由之前的变量覆盖
那么array_walk_recursive($data, [$this, 'filterValue'], $filter);会调用filterValue方法
之后就通过回调函数进行RCE
该调用链的payload:
http://xxxxxx.xxx/public/?s=captcha POST: _method=__construct&filter[]=system&method=get&get[]=whoamiPOC 2
跟进一下method()方法,这里的$method是true:
继续跟进server方法
这里也有input方法
return $this->server('REQUEST_METHOD') ?: 'GET';
这里的name为REQUEST_METHOD,第一个参数$this->server可以利用之前__construct()方法进行属性覆盖,设置server[REQUEST_METHOD]=whoami,之后会调用到getFilter(),于是分析思路和上一个相似,最终调用回调函数进行RCE
总结来自一位师傅:
该复现过程基本是跟着Y4师傅来的,很多地方现在理解也不是很清楚,希望以后能继续完善吧!
继续努力!