其实去年开始是复现过这个漏洞的,但是总觉得并没有吃透,分析写得漏洞百出,于是再来审计一遍。
5.x < 5.1.31
5.x < 5.0.23
php7.3 thinkphp5.1.29 phpstorm
测试成功
进入入口文件,
$this->dispatch
无初始值。
直接跟进路由检测routeCheck
直接跟进 path()
方法
跟进pathinfo()
pathinfo
从当前类中的 config
中获取,
也就是我们可以通过 ?s的方式传入路由信息,这在tp的文档中也有说明。
然后我们继续跟进此处的路由检测。
跟进route类中的 check 方法。
在检查完路由后会返回
url
类,这个类是继承 dispatch
类,然后会执行这个对象的init()
方法。
然后又会实例化 Module
类并执行 init()
方法返回,init() 返回值是当前实例化的对象。
所以我们在一开始的路由检测后
$dispatch
值是 实例化的Molude
对象。
继续往下看,当程序执行到这里时,
$data
在上面已经被赋值为null,会去执行Module
类的 run()
方法,run()
方法在其父类中被调用。
又会去执行 exec()
方法,
继续跟进
注意这里,又去调用了当前app
的controller
方法,
继续跟进 parseModuleAndClass
方法
如果控制器的名字中存在 \
或者以\
开头,会被会被当作一个类,可以利用命名空间,实例化任意类。。
回到controller
方法,会检查类是否存在,存在就会去调用__get()
方法,其实一开始有很多地方都会调用到 __get 方法,比如这些
$this->route->check() $this->request->module()
,这些属性不存在,就会去调用 __get()
方法,
make
方法用来将类实例化。
继续看module
类里exec
方法的后半部分,
获取我们的操作名,也就是我们需要执行的实例化类的方法,然后方法里面对应的参数通过get
请求传入。
tp的路由规则是 ?s=模块/控制器/操作名
在container
类里,存在 invokeFunction
方法,用来动态调用函数。
payload:
?s=index/\think\app/invokefunction?function=call_user_func&vars[0]=system&vars[1]=whoami
在 request 类里,其实也有一个很好的rce利用点,
跟进 filterValue()
这里存在回调函数,且参数可控。
payload
?s=index/\think\request/input?data[]=-1&filter=phpinfo
可以利用此方法写马,
payload
?s=index/\think\template\driver\file/write?cacheFile=shell.php&content=<?php%20phpinfo();?>
然后当前目录访问shell.php 就ok了。
这个rce漏洞归根结底就是因为 把控制器名字的 \ 开头作为类名导致我们可以实例化任意类,后面的payload也不过是基于此漏洞的利用。如有问题还请及时告知。