漏洞分析: ThinkPHP_6.0.13_文件包含漏洞
本文首发于奇安信攻防社区:https://forum.butian.net/share/2059
本文仅用于技术讨论与研究,文中的实现方法切勿应用在任何违法场景。如因涉嫌违法造成的一切不良影响,本文作者概不负责。
0x00 漏洞简介
2022年12月出的漏洞,影响很大但目前没有 CVE
编号,起源是某大佬在跳跳糖发的一篇文章,ThinkPHP
在开启多语言的情况下,存在文件包含漏洞,配合 pearcmd
可以 getshell
,大佬牛*。
0x01 漏洞影响
ThinkPHP5.x
全版本,现已修复,修复链接: https://github.com/top-think/framework/commit/450450a1e38c5897c8d1cbd167068660632fdb27
ThinkPHP6.0.13
及之前,6.0.14
版本已修复
0x02 环境搭建
本文以 ThinkPHP5.1.41
为例。
使用 composer
快速搭建 thinkphp5.1.41
系列最新版环境
1 |
|
这样安装的 topthink/framework
使用的是最新版已经修补了的,因此我们需要进入 tp5.1.41
文件夹,将 composer.json
中的 "topthink/framework": "5.1.*"
修改为 "topthink/framework": "5.1.41"
,然后执行 composer update
此漏洞需要开启多语言功能才可以使用,在 5.x
系列开启的方式如下
文件 config/app.php
中可以设置
1 |
|
0x03 漏洞分析
分析部分将根据官方的修复来寻找漏洞点位,这也是常用的寻找 1day
的方式。
我们可以先看看官方的修复:https://github.com/top-think/framework/commit/450450a1e38c5897c8d1cbd167068660632fdb27
这里 commit
的时候就很明显地说了是改进多语言判断机制,修复的代码实际上就是在做一件事,对 $langSet
进行过滤。
接下来我们跟进代码进行分析,上面的代码位于 thinkphp/library/think/Lang.php
文件中的 detect
方法
我们此处是存在漏洞的代码,也就是没有进行过滤的,修复代码是在 219 行对 $langSet
增加过滤
先看 $langSet
是如何得到的,有三种方式:
第一种方式,直接 GET
方式获取,参数名为 $this->langDetectVar
的值,这个值的默认定位如下
1 |
|
也就是 GET
一个 lang
即可获取 langSet
第二种方式,通过 COOKIE
获取,参数如上也是 lang
第三种方式,通过 $_SERVER['HTTP_ACCEPT_LANGUAGE']
,这也是我们可以控制的,但是他存在过滤代码,因此这里我们不好利用
根据如上三种方式,那么这个 $langSet
是轻易可控的
看到下面的代码,判断如果 $this->allowLangList
是否为空,或者 $langSet
是否在该数组中,这里的 $this->allowLangList
默认是为空的,因此会进入 if
语句内部,这时就会将我们可控的 $langSet
直接赋值给 $this->range
,然后将其返回
通过上述代码,可知存在漏洞的版本中,这个 detect
方法的返回值是完全可控的,接下来我们就是去找此方法的调用位置,直接搜索即可
第二条就是调用 detect
方法,跟进代码
代码中加载了当前的配置,然后查询 app.lang_switch_on
,这是开启多语言机制的标志,当返回为 true
时即可调用到我们可控的 detect
方法,这时就会使得 $this->lang
中的 range
变量是可控的,跟进 465 行
$this->lang->range()
方法
返回 $this->range
,这个值前面说过了,完全可控,继续跟进
$this->request->setLangset()
方法,参数是上面的可控值
直接将参数 $lang
赋值给了 $this->langset
,因此现在 $this->langset
可控
接下来回到上面的加载系统语言包
看到 $this->request->langset()
直接返回 $this->langset
,而这个值可控,我们可以通过设置成 ../../xxx
的形式获取到任意位置的 php
文件,限制了后缀,因此只能是 php
文件
因此 $this->lang->load()
方法的参数可以是任意位置的 php
文件组成的数组,跟进该方法看看
代码很容易看懂,遍历传进来的数组,最后 include
该文件,因此这里我们可以实现包含任意位置的 php
文件
最后就是寻找 loadLangPack
方法调用的位置,发现在 thinkphp/library/think/App.php
的 initialize
方法中,直接被调用
这个 initialize
方法是初始化 thinkphp
的应用,运行时一定会调用。
由上面的就能知道该漏洞只要在开启了多语言机制,访问任何页面都会触发。调用链如下
1 |
|
0x04 漏洞复现
该漏洞有个最大的限制点就是被包含的文件后缀一定是 php
,要包含一个 php
文件,利用的方式就少了,但我们可以利用 pearcmd
,参考链接:https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html#0x06-pearcmdphp
利用 pearcmd
需要满足条件:
- 安装了该
pear
库 - 开启
register_argc_argv
配置
常规的环境一般很少会安装 pear
库,也不会开启 register_argc_argv
配置,但是 php
的官方 docker
环境默认会有该库,并且 register_argc_argv
也会被打开,这里我开了一个使用 php
官方 docker
环境的 docker
,可以看到默认有 pear
此处就借助该 docker
进行漏洞复现,使用了官方的 docker
,就只需要打开多语言即可,然后就可以进行利用,pearcmd
的利用方法就不多讲了,可以看上面提供的链接
这时候就可以看到 /tmp
目录下写入了一个 hello.php
文件
其实这里也不是单纯地将 <?=phpinfo()?>
写入了,而是一个序列化后的数据,但对 php
执行不影响,我们再利用本文的漏洞对该 /tmp/hello.php
文件进行包含
0x05 总结
总体来说,感觉这个洞的限制还是比较多的,多语言机制可能还好,开这个的估计也不在少数,但是文件包含的限制比较不好弄,因为限制了 php
的后缀,虽然可以通过 pear
来 getshell
,但是一般环境默认不存在,让这个洞 getshell
的几率大大降低了。