第一次进行代码量这么大的分析,记录一下,个人感觉新手真的不适应这种,应该找点小一点的cms去分析,如果不懂MVC架构真的可能会懵。。。
前言
在分析这个之前还看了两篇tp5的RCE漏洞,这两个洞都是很相似的,都是利用一个可控的变量dispatch去实现到最后还是构造出回调函数,可以学习一下,感觉这里面的思路就是本文分析漏洞的来源
我这里已tp 5.0.22为例子,环境是phpstudy搭建的
补丁
影响版本
THINKPHP 5.0.5-5.0.22
THINKPHP 5.1.0-5.1.30
5.0.x补丁地址:https://github.com/top-think/framework/commit/b797d72352e6b4eb0e11b6bc2a2ef25907b7756f
5.1.x补丁地址:https://github.com/top-think/framework/commit/802f284bec821a608e7543d91126abc5901b2815
漏洞分析
补丁中加了正则限制了控制器的自定义初始化
payload:
localhost/tp52/public/?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=dir
根据补丁下的点,动态跟踪一下是否是因为controller没有做好过滤而实体化,确实是如此
根据传进去的payload,控制器以及下面的方法都会发生对应的变化,下面就可以分析一下攻击链的流程
可以从入口文件一级一级跟踪,进入到App.php
中,这里应该涉及到一个开发的知识,在App.php
中,会根据请求的URL调用routeCheck
进行调度解析在App.php
中,会根据请求的URL调用routeCheck
进行调度解析获得到$dispatch
,所以payload是一定要经过那里的,可以在那里加断点进行调试
定位到/thinkphp/library/think/App.php:116
$dispatch = self::$dispatch;
// 未设置调度信息则进行 URL 路由检测
if (empty($dispatch)) {
$dispatch = self::routeCheck($request, $config);
}
// 记录当前调度信息
$request->dispatch($dispatch);
......
$data = self::exec($dispatch, $config);//这个函数很关键
继续跟进routeCheck
这个函数,同样在App.php里面
继续跟进到path
方法里面,然后这里有一个pathinfo()函数,继续跟进
public function path()
{
if (is_null($this->path)) {
$suffix = Config::get('url_html_suffix');
$pathinfo = $this->pathinfo();
if (false === $suffix) {
// 禁止伪静态访问
$this->path = $pathinfo;
} elseif ($suffix) {
// 去除正常的URL后缀
$this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);
} else {
// 允许任何后缀访问
$this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo);
}
}
return $this->path;
}
Config::get('var_pathinfo')
是配置文件中的设置的参数,默认值为s
,怎么找到这个变量?可以全局搜索一下,可以搜索到其中一个配置文件里面有
从GET中获取键值,然后赋值给routeCheck
中的$path
,这里也就是index/think\app/invokefunction
。
然后开始进入路由检测的部分,经过check的检查后会进入else的分支,但这一部分对于我们需要控制的变量没有任何影响,关键是$result
以及$must
这两个变量的赋值结果,这也是导致了后面操作的关键,可以进入Route::parseUrl
函数
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'];
if ($check) {