当前位置:网站首页>Thinkphp 5.0.24变量覆盖漏洞导致RCE分析
Thinkphp 5.0.24变量覆盖漏洞导致RCE分析
2022-08-01 23:44:00 【bhegi_seg】
大概思路就是我们可以修改requests类的filter属性、method属性以及get属性的值,从而在调用param方法时,call_user_func_array的值我们就可以控制,造成了远程代码执行漏洞。
0. 大致流程
经过入口文件进入run函数
首先在116行根据url获取调度信息时,触发变量覆盖漏洞从而修改requests对象的属性值,然后获取s=captcha的调度信息并返回给$dispatch
再到139行进入exec函数并将$dispatch作为参数带入
跟进后根据$dispatch的type进入到case ‘method’,从而调用requests的param函数,进而造成了rce漏洞
1.环境搭建
这里用的环境是thinkphp5.0.20+php5.6.27+apache+phpstorm
POC:
复现成功
1.1 POC参数解析
method=get 因为captcha的路由规则是get方式下的,所以我们得让method为get,才能获取到captcha的路由
s=captcha 因为在进入exec函数后我们要switch到method中执行param函数,而这个captcha的路由刚好对应类型为method,所以我们选择captcha
filter[]=system 覆盖变量
get[]=whoami 覆盖变量
_method=__construct 为了能够进入construct,从而覆盖变量
2.漏洞分析
这是一个变量覆盖漏洞导致的rce,我们首先来说下变量覆盖漏洞
2.1 变量覆盖漏洞
漏洞触发点在thinkphp/library/think/Request.php的509行:
这里509行的 t h i s − > m e t h o d 我们可控, ∗ ∗ 该值就来自与上一行的 this->method我们可控,**该值就来自与上一行的 this−>method我们可控,∗∗该值就来自与上一行的_POST[Config::get(‘var_method’)],其中Config::get(‘var_method’)的值是_method**
我们post传入_method为__construct,就会调用request对象的构造函数,参数为post内容
跟进__construct,可以看到他将传入的参数依次赋值给相应的属性,这就造成了变量覆盖漏洞,我们可以随便给requests对象的属性赋值,这为后面的rce打下基础。从poc也能看出来,为了能够rce,这里我们需要修改的属性值有filter,get,method,_method
2.2 远程代码执行
RCE的触发点在thinkphp/library/think/Request.php的param函数:
第一个if是获取post的提交内容并赋值给$vars
然后再整合一下赋值给requests对象的param属性
最后调用该类下的input方法,跟进input方法,$data的值为上图红框
public function input($data = [], $name = '', $default = null, $filter = '')
{
if (false === $name) {
// 获取原始数据
return $data;
}
$name = (string) $name;
if ('' != $name) {
// 解析name
if (strpos($name, '/')) {
list($name, $type) = explode('/', $name);
} else {
$type = 's';
}
// 按.拆分成多维数组进行判断
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;
}
执行到这一行:$filter = t h i s − > g e t F i l t e r ( this->getFilter( this−>getFilter(filter, $default);
跟进,看代码意思就是将 t h i s − > f i l t e r 的值赋给 this->filter的值赋给 this−>filter的值赋给filter变量并返回,这个$this->filter是我们可控的即[“system”]
回到input函数,这时候$filter为[“system”]
往下走,$data确实是数组,所以进入if
走到array_walk_recursive(KaTeX parse error: Undefined control sequence: \[ at position 7: data, \̲[̲this, ‘filterValue’], $filter);
array_walk_recursive这个函数大概意思是每次从data中取一个值(第一次的取值由上面图红框可知是whoami),应用于第二个参数所指示的函数
跟进到filterValue方法,看到此时 f i l t e r 为 " s y s t e m " , filter为"system", filter为"system",value为whoami,走到1073行即可执行我们设置的回调函数并将结果赋值给value
最终返回给我们value值,这就造成任意代码执行漏洞
这就是因为我们覆盖了requests对象的属性值导致的rce漏洞
3.具体流程
当我们执行poc后,从入口函数开始分析
首先加载框架的引导文件
往下走,首先加载基础文件。
然后执行app类的run函数
public static function run(Request $request = null)
{
$request = is_null($request) ? Request::instance() : $request;
try {
$config = self::initCommon();
// 模块/控制器绑定
if (defined('BIND_MODULE')) {
BIND_MODULE && Route::bind(BIND_MODULE);
} elseif ($config['auto_bind_module']) {
// 入口自动绑定
$name = pathinfo($request->baseFile(), PATHINFO_FILENAME);
if ($name && 'index' != $name && is_dir(APP_PATH . $name)) {
Route::bind($name);
}
}
$request->filter($config['default_filter']);
// 默认语言
Lang::range($config['default_lang']);
// 开启多语言机制 检测当前语言
$config['lang_switch_on'] && Lang::detect();
$request->langset(Lang::range());
// 加载系统语言包
Lang::load([
THINK_PATH . 'lang' . DS . $request->langset() . EXT,
APP_PATH . 'lang' . DS . $request->langset() . EXT,
]);
// 监听 app_dispatch
Hook::listen('app_dispatch', self::$dispatch);
// 获取应用调度信息
$dispatch = self::$dispatch;
// 未设置调度信息则进行 URL 路由检测
if (empty($dispatch)) {
$dispatch = self::routeCheck($request, $config);
}
// 记录当前调度信息
$request->dispatch($dispatch);
// 记录路由和请求信息
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');
}
// 监听 app_begin
Hook::listen('app_begin', $dispatch);
// 请求缓存检查
$request->cache(
$config['request_cache'],
$config['request_cache_expire'],
$config['request_cache_except']
);
$data = self::exec($dispatch, $config);
} catch (HttpResponseException $exception) {
$data = $exception->getResponse();
}
// 清空类的实例化
Loader::clearInstance();
// 输出数据到客户端
if ($data instanceof Response) {
$response = $data;
} elseif (!is_null($data)) {
// 默认自动识别响应输出类型
$type = $request->isAjax() ?
Config::get('default_ajax_return') :
Config::get('default_return_type');
$response = Response::create($data, $type);
} else {
$response = Response::create();
}
// 监听 app_end
Hook::listen('app_end', $response);
return $response;
}
首先实例化一个requests对象,这个包含了请求的相关信息
往下走到115行,因为我们并没有调度信息,则进入routeCheck函数进行url路由检测产生该url的调度信息,这个调度信息就是匹配s=captcha对应的路由和类型值供后面的exec函数使用
进入函数,首先将url传给$path
然后设置分割符,到643行进行路由检测,根据定义好的路由返回对应的url调度信息
进入check函数,走到848行,注意这里就是我们触发变量覆盖漏洞的点
进入method函数,接下来就是最上面讲的变量覆盖了,这里就不在多赘述
这个函数执行完会返回我们设置的$this->method即GET,注意这里设置为get是为了获取到s=captcha的路由规则
返回到check函数,跟进到863行,这里给$item赋值
然后检查是否存在$item的路由规则,我们先看一下路由规则里面都有啥,就只有一个当访问captcha/[:id]时路由为 [email protected]
这个路由规则是在vendor opthink hink-captchasrchelper.php定义的,这也就是为啥我们将method设置为get,因为只有这样才能获得captcha的路由规则
好了,回到正题,继续跟进到877行,开始路由规则以及类型匹配!!!!!!!!
进入checkRoute函数后走到955行,调用checkRule
走到1194行调用parseRule函数,注意看此时 r o u t e 参数为 h i n k c a p t c h a C a p t c h a C o n t r o l l e r @ i n d e x 已经匹配到路由 ∗ ∗ ∗ ∗ 进入这个函数看看要干嘛,这个函数太长了,直接看精华部分,根据 r o u t e 的取值我们进入红框分支,在这个分支中, ‘ route参数为 [email protected]已经匹配到路由** **进入这个函数看看要干嘛,这个函数太长了,直接看精华部分,根据route的取值我们进入红框分支,在这个分支中,` route参数为hinkcaptchaCaptchaController@index已经匹配到路由∗∗∗∗进入这个函数看看要干嘛,这个函数太长了,直接看精华部分,根据route的取值我们进入红框分支,在这个分支中,‘result的’type’键对应的值为‘method’。然后将
r e s u l t ‘ 层层返回到 r u n 函数中,并赋值给了 ‘ result`层层返回到run函数中,并赋值给了` result‘层层返回到run函数中,并赋值给了‘dispatch`。
返回到最开始的run函数,可以看到$result已经包含了captcha的调度信息
继续往下走,因为我们没开debug模式所以123行直接跳过,如果我们开启了debug模式,则直接在126行就可以进入到param函数执行rce
走到139行,带着调度信息进入exec函数
exec函数,因为我们的$dispatch[‘type’]为method,所以进入case ‘method’
protected static function exec($dispatch, $config)
{
switch ($dispatch['type']) {
case 'redirect': // 重定向跳转
$data = Response::create($dispatch['url'], 'redirect')
->code($dispatch['status']);
break;
case 'module': // 模块/控制器/操作
$data = self::module(
$dispatch['module'],
$config,
isset($dispatch['convert']) ? $dispatch['convert'] : null
);
break;
case 'controller': // 执行控制器操作
$vars = array_merge(Request::instance()->param(), $dispatch['var']);
$data = Loader::action(
$dispatch['controller'],
$vars,
$config['url_controller_layer'],
$config['controller_suffix']
);
break;
case 'method': // 回调方法
$vars = array_merge(Request::instance()->param(), $dispatch['var']);
$data = self::invokeMethod($dispatch['method'], $vars);
break;
case 'function': // 闭包
$data = self::invokeFunction($dispatch['function']);
break;
case 'response': // Response 实例
$data = $dispatch['response'];
break;
default:
throw new InvalidArgumentException('dispatch type not support');
}
return $data;
}
进入requests的param函数
然后就是之前2.2节分析的rce流程了,不再赘述。
4.参考链接
https://paper.seebug.org/787/
https://www.freebuf.com/vuls/307413.html
https://xz.aliyun.com/t/8143#toc-6
https://www.kancloud.cn/zmwtp/tp5/119426
先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦
边栏推荐
- 仿牛客网项目第三章:开发社区核心功能(详细步骤和思路)
- Use Jenkins for continuous integration, this knowledge point must be mastered
- 颜色透明参数
- Deep Learning Fundamentals - Numpy-based Recurrent Neural Network (RNN) implementation and backpropagation training
- PostgreSQL Basics--Common Commands
- What is CICD excuse me
- ICLR 2022 Best Paper: Partial Label Learning Based on Contrastive Disambiguation
- Sql之各种Join
- Solve the port to take up
- 架构基本概念和架构本质
猜你喜欢
Share an interface test project (very worth practicing)
软件测试之移动APP安全测试简析,北京第三方软件检测机构分享
测试岗月薪5-9k,如何实现涨薪到25k?
2022还想上岸学习软件测试必看,测试老鸟的肺腑之言...
机器学习文本分类
cdh6打开oozieWeb页面,Oozie web console is disabled.
技术分享 | 接口测试中如何使用Json 来进行数据交互 ?
[LeetCode304 Weekly Competition] Two questions about the base ring tree 6134. Find the closest node to the given two nodes, 6135. The longest cycle in the graph
【MySQL系列】MySQL索引事务
程序员如何优雅地解决线上问题?
随机推荐
Solve the port to take up
添加大量元素时使用 DocumentFragments
Flink Yarn Per Job - CliFrontend
Flink学习第五天——Flink可视化控制台依赖配置和界面介绍
Calculate the angle of a line defined by two points
6132. All the elements in the array is equal to zero - quick sort method
Always use "noopener" or "noreferrer" for links that open in a new tab
Chapter 19 Tips and Traps: Common Goofs for Novices
如何用Redis实现分布式锁?
TCP 可靠吗?为什么?
Flink Yarn Per Job - 提交流程一
洞见云原生微服务及微服务架构浅析
[LeetCode304周赛] 两道关于基环树的题 6134. 找到离给定两个节点最近的节点,6135. 图中的最长环
仿牛客网项目第三章:开发社区核心功能(详细步骤和思路)
Flink学习第四天——完成第一个Flink 流批一体案例
6134. Find the closest node to the given two nodes - force double hundred code
机器学习文本分类
drf生成序列化类代码
@Resource和@Autowired的区别
请问什么是 CICD