Thinkphp5.0.22 命令执行分析

从现在开始慢慢的分析thinkphp类漏洞,学习一下大的框架下的漏洞

Posted by BY Diego on June 7, 2020

一、 POC

第一种

http://127.0.0.1/tp5.0.22/public/?s=captcha

POST 如下
_method=__construct&filter[]=system&method=get&get[]=whoami

第二种
tp5.0.22/public?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami

或者多加个\  ?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami

二、影响版本

5.0.5-5.0.22

三、原理分析

不得不说分析的我头皮发麻 ,还有模糊的地方,测试版本为5.0.22完整版 t27QWF.jpg

直接带poc然后调试

①、 第一种情况

程序入口 public/index.php t2gW7Q.png

public/start.php 调用App类的run方法 t2g2nS.png

run 方法有个routeCheck方法和 exec方法 t2gR0g.png

跟进routeCheck方法 如下又调用了Route的check方法 t2RfWn.png

继续跟check里的 Request method方法 t2RWJs.png

本次的重点之一来了 在method方法里, $this->method 的值是可以通过thinkphp的请求类型伪装来获取 t2RgoQ.png

手册说明 t2Rcdg.png

配置文件是这样的 t2RRij.png

所以通过post 给_method赋值,就可以控制$this->method 的值 因此通过红色部分就可以调用该类中任意方法。 通过payload可以知道_method=__construct 调用的是__construct方法$this->__construct($_POST)

通过遍历 如果类中存在与post同名称的 变量就会进行赋值 t25FDP.png

赋值结果如下,请求的路由是?s=captcha,它对应的注册规则为\think\Route::get。在method方法结束后,返回的$this->method值应为get这样才能不出错,所以payload中有个method=get t25EE8.png

第一大步就完成了 然后一步一步退回到App的run方法 通过控制?s=captcha使得dispatch=method, t2oqun.png 然后执行exec,跟进 然后进入method 的case t2ovNT.png

调用Request::param方法,会进入input方法 t2oLBq.png

各个变量如下 t2oOH0.png

一系列之后进入filterValue方法 t2ojEV.png

最后构造的参数被call_user_func调用,因此执行了命令 t2T4q1.png

t27iRg.png

②、第二种情况

一开始会调用routecheck,跟进 t4slB8.png

然后一系列操作后进入 Route::parseurl t4s8Ag.png

这里的pasreUrlPath会对请求中的 index/\think\app/invokefunction 进行解析 function=call_user_func_array&vars[0]=system&vars[1][]=whoami 会在请求参数部分中解析,具体过程就不分析了

会把index/\think\app/invokefunction/为分隔符,分成['index','\think\app','invokefunction']t4s1HS.png

下一步分别将上一步结果赋值给三个变量,根据thinkphp 路由规则 index 为模块 、\think\app 为控制器、invokefunction为操作,最后封装成路由 t4sJhj.png

退回到一开始 App::run(),然后执行exec t4IbGV.png

因为返回值中为module,因此进入黄色部分 t4IHP0.png

跟进module 方法,黄色部分检测module 是否存在,不存在则报错。综上来看只要存在,就可调用任意module t4IT5q.png

然后查看利用方法,模块检测完之后就是控制器 继续跟进 t4qkbF.png

构造控制器的过程调用了Loder::controller 方法 t4qFDU.png

然后又调用了self::getModuleAndClass 该方法就是获取Module、Class的,这里通过判断$name 中是否存在 \,若存在class就是$name,此时$name\think\App,因此$class=\think\App,至此就成功调用了 App类 t4qCvV.png

t4q9g0.png

控制器之后就是操作了,跟进self::invokeMethod() t5fWaq.png

该方法里用了ReflectionMethod来构造App类的invokefunction方法,然后就是调用App::invokefunction() t5fRZn.png

最后就是执行命令的地方,利用ReflectionFunction,来构造自己想要的函数执行即可,$function$vars,可以用过get获取 t5fgqs.png

因此通过url传参function=call_user_func_array&vars[0]=system&vars[1][]=whoami

t5I40J.png

当时还有个疑惑 为啥不直接function=system&vars[]=whoami,于是试了试直接报错了,追其原因发现是,在获取$args时会对函数所有的参数进行赋值 t5oU41.png t5od9x.png t5ovvT.png

因此需要两个参数,查看手册发现第二个参数必须为变量,因此通过传参是达不到目的的 t57lmF.png t57MOU.png 同时发现shell_exec,因此可以直接调用shell_exec但是没有回显示,可以弹个计算器验证一下 t57ulV.png t57KyT.png

多个参数的同样可以,只要不是变量就行,md5加密举例 t571w4.png

t5HmAH.png

t5HZHe.png

为了省去麻烦的步骤因此payload才用的call_user_func_array