从0学代码审计——thinkphp 5.0.23

xilitter   ·   发表于 2023-12-13 09:24:58   ·   代码审计

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的执行过程。

用户名金币积分时间理由
Track-魔方 100.00 0 2023-12-13 20:08:54 100 继续加油~

打赏我,让我更有动力~

0 条回复   |  直到 2023-12-13 | 595 次浏览
登录后才可发表内容
返回顶部 投诉反馈

© 2016 - 2024 掌控者 All Rights Reserved.