Thinkphp5代码审计(二)未开启路由rce
thinkphp5.0.18 rce
配置文件默认是没有开启强制路由的

浏览器访问网页后,进入框架入口

在入口处,只有两个步骤,第一步引入了convertion的配置

跟进第二步,看看框架怎么执行的,run方法里面initCommon会生成配置信息

跟进

判断了配置文件的类型,如果是php文件,就执行一遍set方法

这样就加载了配置信息,加载完配置信息后,程序返回run方法继续执行,在116行进入了routeCheck方法

routerCheck方法,608行path方法内的pathinfo判断了url访问模式,并返回了处理后的路径信息,随后加载了路由配置

如果是强制路由模式,就会抛出错误

这里我用完整的url,/public/index.php/index/Index/index

在parseUrl方法内,把url处理后返回了路由信息的数组

最后这些信息传进了exec
在thinkphp3里exec方法会使用eval或者include渲染网页,我们看看thinkphp5是怎样的

渲染在module里完成的

跟进module,在module方法内的第567行,把url传入的信息转换成了控制器的路径

跟进controller,这里有一个过去模块和控制器信息的方法getModuleAndClass

这里第一个分支比较重要,是区分pathinfo模式和兼容模式的

如果这里没有反斜杠\,就会进入parseClass方法,强制在模块后面加上controller

这时候执行的类,就是命名空间内app\index\controller\Index类


如果要但是这个命名空间是开发者自己写的,可以直接利用的可能性不大,所以回到getModuleAndClass方法,想办法进入think库,这里构造一个url去访问/thinkphp_5.0.15/thinkphp/library/think/App.php内的invokeFunction,至于为什么访问这个方法,因为up看到payload上是这个方法,说明里面可以执行命令。
构造思路如下:
1.模块名似乎没有影响,还是用index
2.app.php的命名空间是think,所以控制器名是think\app

3.方法名invokeFunction
4.pathinfo模式不能使用反斜杠,会被替换成斜杠,所以url要使用兼容模式格式
浏览器访问下试试:

这样我们就能控制访问的类了,在把控制器名称返回后,又在581行使用判断了is_callable函检查invokefunction方法是否存在于控制器

再跟进595行,看看invokeMethod内做了什么

先是通过ReflectionMethod类获取了源代码中app类的信息,随后在339行,bindParams方法获取url中的参数存储进vars变量,然后遍历方法的参数,创建一个数目对应的数组

如果url中有invokeFunction需要的参数名,就传递进去,否则就抛出缺少参数的异常

构造参数的方法需要分析invokeFunction的源码,从源码中看到,invokeFunction需要两个参数,第一个是function,第二个是vars

所以url里需要有function和vars参数,参数值我们先任意填写,这时候再次访问,程序会执行到343行的invokeArgs

ReflectionMethod里指定了反射的方法是think\app类中的invokeFunction
invokeArgs是ReflectionMethod类中的一个内置方法,接受两个参数,第一个是think\app类对象,第二个是传递给反射方法的参数(数组形式),返回方法执行的结果

所以invokeArgs是把vars反射到invokeFunction了,跟进invokeFunction

这里会使用ReflectionFunction反射一个函数, ReflectionFunction用法和ReflectionMethod差不多,这里选择call_user_func_array,为什么system函数不行,继续跟进下去就明白了,跟进bindParams,里面有一个reset函数

system接受的字符串类型的参数,但是reset方法是处理数组或者对象的,传入字符串会报错,所以不能反射system参数,需要通过反射call_user_func_array函数,调用system。
这里我们传递进一个vars数组,构造方法参考手册

vars数组第一个元素是字符串类型,第二个元素是数组类型,在url里就如下构造
这时候在浏览器使用我们完整的payload:
这是反射进call_user_func_array的参数

ReflectionFunction::invokeArgs()用法如下:

反射完毕后,把命令执行的结果返回:


篇幅过长,本来想多写几个洞的,没想到这次审计这么难,个别难点不看别人payload还没想不出来。