0x01 前言
ThinkPHP 是一款开源的 PHP 框架,用于快速、简单地开发 PHP 应用程序。它提供了一套丰富的功能和工具,使开发者能够更容易地构建各种规模的 Web 应用。ThinkPHP 的目标是提高开发效率,同时保持代码的可读性和可维护性。thinkphp的许多版本中也爆出了多个漏洞,本篇文章主要记录thinkphp 5.0.23版本 RCE的漏洞复现。
0x02 thinkphp RCE分析
thinkphp的开发框架就不说了,RCE的分析会说的详细点,在此只记录这次分析过程中我对于thinkphp的认识。
主要有两个poc,其实都大同小异
poc1
/?s=captcha
POST
_method=__construct&filter[]=system&method=get&get[]=whoami
贴个执行截图
还有类似验证码的图片,怎么来的
下断点调试,打入poc看看thinkphp内部的一个执行流程。
断点下在哪,thinkphp框架都有一个入口文件index.php,在public目录下,既然我们要在该文件下传数据,那么就在该文件下断点。
这里会调用 start.php 引导文件
执行app.php的run方法,app.php用于配置应用程序的全局设置和参数,我们跟进run方法,跟进之后会进入到Loader.php,
这里判断是否存在对应的类文件,是否是windows环境,然后就包含这个文件。
接下来进入到run方法,这里会创建一个Request类的实例,Request 就是处理请求的类,继续跟进
又需要 Loader 去包含,然后走到Request类的构造方法
这里将POST数据写入到input变量里,
创建完Request实例后,进入到 routeCheck 方法里
这里获取到path,跟进方法
这里做了一个判断,从Config类里获取到varpathinfo的值,然后检查$_GET全局变量里是否存在这个变量,我们不妨跟进这个get方法去看一下
$range是_sys,name属性的值就是var_pathinfo,对应config默认值就是s
那么所获取到的path就是captcha 然后走到路由检查这块地方
时刻关注与request有关的代码,因为可控的地方只有我们请求的数据
这里跟进到check方法
在857行这里,执行的request实例的 method 方法
这里,在config配置类中var_method的默认值就是
然后从$_POST全局变量中找键名为_method的值,这里存在任意函数调用,很关键,我们当前所传的值就是request类的构造方法,这里跟进
遍历键值对,然后判断键名在当前类中是否存在,若存在,就覆盖掉键值。
实际上该类的filter属性是没有空的,我们传的 filter[]=system 会在此刻覆盖掉原有的值,参数值的覆盖同理,又是一个关键的一步。poc中method=get 是将 method 的值给改回来,防止报错。
走到App类的exec方法,应该对应路由的调用,跟进
这里会走到这个分支,还像是captcha路由影响的,至于为什么,代码还没调出来,先留个坑。
然后跟进到 param 方法
$this->mergeParam 为true,进入不到 if 里面,那么进入到input方法
首先是对$name做一些格式 上的处理
然后判断$data是否为数组,满足条件就调用 filterValue 方法,此时的$data和$filter为
弹出filter数组的最后一个元素,然后遍历数组,call_user_func函数调用达到任意命令执行的目的。
poc2
大同小异?s=captcha
POST
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=whoami
在 param 方法中
又有一处method方法的调用,不过参数设置为true了,跟进
调用了 server 方法,参数是 REQUEST_METHOD
最后也会走到input,任意函数调用。
补坑
来补坑了,写完这篇文章之后不甘心,打算又调一下代码,这次算是调明白了。
坑:为什么要get传一个 ?s=captcha
结论:为了方便使 $dispatch=method 从而进入 Request::instance()->param(),当时不懂,为什么传一个captcha 就能让$dispatch=method???
就是在这里,所对应的$dispatch[‘type’]就是method
继续往上跟,看它是由什么赋值
在118行,调用routeCheck方法获取路由调度,这个方法返回的是 $result 那么继续看$result是由谁赋值
路由检查方法的返回值赋给 $result 调试跟的话实际上会走到这里
那么就需要看checkRoute函数的返回值了
此时遍历$rules的下标,有两个元素
拆分,解析路由相关参数
然后跟进到这个函数
在这个match函数里,将 $rule 的内容以斜杠分隔符拆分为数组,然后遍历,经过一系列的判断
这是一个关键点,这里判断$val 和 $m1[$key] 是否不相等,若不相等,返回非0,然后会 return false;所以在get传参上,一定要让s=captcha,至于为什么是参数s,因为在config类中默认参数就是s了。
这里进入不了 elseif 就会遍历下一个元素,
成功匹配后就会调用到 parseRule函数来解析路由
最后就是让 $dispatch[‘type’]=method了,回到了结论。
而如果我们get传入的不是 captcha,在match函数匹配规则的时候就会返回false,然后一路返回false
进入到这个if条件,然后调用 parseUrl 函数
最后返回的 type 是module,在主要执行流中就走不到call_user_func函数了。
0x03 结语
thinkphp没有对用户输入的方法名进行过滤和限制,导致可以任意函数调用,(此时的任意函数只是在Request类中)调用Request类的构造方法达到变量覆盖,最终call_user_func任意代码执行。
主要还是多调试才能理解poc的执行过程。
免费领取安全学习资料包!
渗透工具
技术文档、书籍
面试题
帮助你在面试中脱颖而出
视频
基础到进阶
环境搭建、HTML,PHP,MySQL基础学习,信息收集,SQL注入,XSS,CSRF,暴力破解等等
应急响应笔记
学习路线