一、 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完整版
直接带poc然后调试
①、 第一种情况
程序入口 public/index.php
public/start.php 调用App类的run方法
run 方法有个routeCheck方法和 exec方法
跟进routeCheck方法 如下又调用了Route的check方法
继续跟check里的 Request method方法
本次的重点之一来了 在method方法里, $this->method 的值是可以通过thinkphp的请求类型伪装来获取
手册说明
配置文件是这样的
所以通过post 给_method赋值,就可以控制$this->method
的值
因此通过红色部分就可以调用该类中任意方法。
通过payload可以知道_method=__construct
调用的是__construct方法
即 $this->__construct($_POST)
赋值结果如下,请求的路由是?s=captcha,它对应的注册规则为\think\Route::get。在method方法结束后,返回的$this->method值应为get这样才能不出错,所以payload中有个method=get
第一大步就完成了 然后一步一步退回到App的run方法 通过控制?s=captcha使得dispatch=method,
然后执行exec,跟进
然后进入method 的case
调用Request::param方法,会进入input方法
各个变量如下
一系列之后进入filterValue方法
最后构造的参数被call_user_func调用,因此执行了命令
②、第二种情况
一开始会调用routecheck,跟进
然后一系列操作后进入 Route::parseurl
这里的pasreUrlPath会对请求中的 index/\think\app/invokefunction
进行解析
function=call_user_func_array&vars[0]=system&vars[1][]=whoami
会在请求参数部分中解析,具体过程就不分析了
会把index/\think\app/invokefunction
以 /
为分隔符,分成['index','\think\app','invokefunction']
,
下一步分别将上一步结果赋值给三个变量,根据thinkphp 路由规则 index
为模块 、\think\app
为控制器、invokefunction
为操作,最后封装成路由
退回到一开始 App::run(),然后执行exec
因为返回值中为module,因此进入黄色部分
跟进module 方法,黄色部分检测module 是否存在,不存在则报错。综上来看只要存在,就可调用任意module
然后查看利用方法,模块检测完之后就是控制器 继续跟进
构造控制器的过程调用了Loder::controller
方法
然后又调用了self::getModuleAndClass
该方法就是获取Module、Class的,这里通过判断$name
中是否存在 \
,若存在class就是$name
,此时$name
为 \think\App
,因此$class=\think\App
,至此就成功调用了 App类
控制器之后就是操作了,跟进self::invokeMethod()
该方法里用了ReflectionMethod
来构造App类的invokefunction
方法,然后就是调用App::invokefunction()
最后就是执行命令的地方,利用ReflectionFunction
,来构造自己想要的函数执行即可,$function
、$vars
,可以用过get获取
因此通过url传参function=call_user_func_array&vars[0]=system&vars[1][]=whoami
当时还有个疑惑 为啥不直接function=system&vars[]=whoami
,于是试了试直接报错了,追其原因发现是,在获取$args
时会对函数所有的参数进行赋值
因此需要两个参数,查看手册发现第二个参数必须为变量,因此通过传参是达不到目的的
同时发现
shell_exec
,因此可以直接调用shell_exec
但是没有回显示,可以弹个计算器验证一下
多个参数的同样可以,只要不是变量就行,md5加密举例
为了省去麻烦的步骤因此payload才用的call_user_func_array