0


thinkphp5 漏洞原理分析合集

thinkphp5 rce原理分析

文末附赠相关综合利用工具

文章中主要介绍以下4种漏洞的原理分析过程:

5.0.X 路由过滤不严谨rce
 5.1.X 路由过滤不严谨rce
 __construct 变量覆盖导致RCE
5.0.X 数据库信息泄露

1. 5.0.x 路由过滤不严谨导致rce

1.1 原理分析

设置兼容模式(pathinfo)路由解析时,没有对”\“进行过滤,导致可以指定任意模块控制器和方法,例如:

  1. http://127.0.0.1/tp5.0/public/index.php?s=index/think/app/invokefunction
    模块:index
    控制器:think
    方法:app
    参数:invokefunction

正常解析会出错,因为没有think中没有app方法。

  1. http://127.0.0.1/tp5.0/public/index.php?s=index/think\app/invokefunction
    模块:index
    控制器:think\app
    方法:invokefunction

加上""后就会将think\app解析成为一个整体,就是think下的app文件。

首先进入入口文件public下的index.php中:

进入start.php中,先加载base.php,再执行app.php中的run方法。

跟进run方法,前面为初始化的一些东西

到这开始进入路由解析,使用的routeCheck方法。

跟进routeCheck方法,先调用Request.php中的path方法,

跟进path方法,调用pathinfo方法,来判断pathinfo(是否为兼容模式)。

跟进pathinfo方法,Config::get(‘var_pathinfo’)查看是否配置了var_pathinfo参数,如果配置了为兼容模式,并提取出默认参数s的值,最后传到app.php中的$path。

下面$config[‘url_route_must’]为查看是否开启了强制路由,如果开启解析’\’时就会失败,就不会产生rce。

进入parseUrlPath方法中,该方法将路由拆分为模块、控制器、方法。这里面并没有过滤’\’。

返回将返回得值赋值给$path,如下图:

提取模块,提取$path中的模块”index”。

提取控制器和方法,提取$path中的控制器”think\app”和方法”invokefunction”。

将拆分的信息封装进$route中,再将$route以数组中值的方式返回给$dispatch。

这块是记录日志、监听、检查缓存,直接略过。

判断$dispatch中$type类型,选择执行module函数。

跟进静态module函数,一些初始化模块。

获取路由中的模块、控制器、方法。

执行控制器中的方法,调用静态方法invokeMethod。

实例化ReflectionMethod方法传入模块、控制器、方法,再通过Request类中的filterValue方法递归出传入的参数。

将递归出的参数,进行递归绑定,并写入$args中。

再调用反射的方法,并传入参数,也就是执行传入的恶意命令。

返回$data

1.3 payload

http://[ip]/tp5/public/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami

2. 5.1.X 路由过滤不严谨rce

由于找不到5.1的低版本,所以只能分析漏洞关键点,大体流程和5.0差不多。

关键点:

路由没有过滤"",导致可以通过""来指定任意模块和控制器。

2.1 原理分析

利用链同样是invokefuntion方法,在think/Container.php中,但与5.0不同的是方法中存在了call_user_func_array函数,该函数可以将出入的第一个值当作回调函数,第二个值为回调函数的参数。 bindParams方法为递归提取传入的参数绑定到$args上。

直接从路由解析那块开始分析,App.php的run方法,调用routeCheck()

跟进routeCheck(),$path将url中的模块控制器方法函数取出来,$must判断是否为强路由模式,$dispatch路由检测,检测模块控制。

routeCheck()返回$dispatch,回到App.php中,$this->routeCheck()->init()就等于$this->$dispatch->init()就等于think\route\dispatch\Url->init()

跟进到think\route\dispatch\Url.php的init()中,调用parseUrl()

跟进parseUrl()中,我这个版本是修复了的,所以多出来的那块代码就是修复代码,低版本中并没有,所以就没有过滤”\“,导致可以调用think/Container中的invokeFuntion方法,导致可以使用call_user_func_array函数执行恶意命令。

2.2 payload

?s=index/think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami

3 __construct 变量覆盖导致RCE

以thinkphp5.0.x为例

关键点

Request.php中method()中,没有对'var_method'进行过滤,用户可控传入__constrct,导致调用__construct析构函数,将传入变量循环覆盖到$this->method="GET",$this->get="whoami",$this->filter="system";如果开启debug,会调用call_user_func导致rce。

3.1 原理分析

我们payload传入

POST http://127.0.0.1/tp5.0/public/index.php?s=index
_method=__construct&filter[]=system&method=GET&get[]=whoami

上面的那些操作就不说了,直奔主题,进行路由检测会调用静态方法routeCheck()

跟进又调用Route类里面的静态方法check()

跟进check(),又调用method()

跟进Request.php中method(),Config::get(‘var_method’)提权var_method中的值,var_method的又为_method,这个_method可控,我们传入’__construct’,到$this->{$this->method}($_POST);中就变为$this->__construct($POST)。

跟进__construst(),该方法将我们POST方法传入的参数通过foreach循环覆盖到相对应的参数上。

返回$method=‘get’。

到self::$rules[$method];,这也是为什么要传入method=GET的原因,因为不传入的的话$method=__construct,如果静态数组$rules内不存在该参数就会报错。

所以我们传入的method可以不是GET,只要在这个数组里面就行。

到下面判断是否开启debug调试模式,调用param()。

跟进Request.php中param(),,将POST中的参数赋值给$vars,又调用get()。

跟进get(),调用input(),传入刚才覆盖的$this->get。

跟进input(),将$this->get的值赋值给$data。

返回到param()中,又调用input()。

跟进input(),将$this->filter赋值给$filter。

再到下面通过array_walk_recursive函数,调用filterValue方法,将$data和$filter的值带入到filterValue方法中。

跟进filterValue(),使用call_user_func回调函数,回调$filter的值system函数,将$value用上面的array_walk_recursive函数遍历传入system函数中执行,并把执行结果传入到$data中。

再到下面,$dispatch[‘type’]的值为’module’,调用静态方法module()。

跟进module(),到最后调用静态方法invokeMethod()。

跟进invokeMethod(),调用静态方法bindParams()

跟进bindParams(),通过Config::get(‘url_param_type’)获取参数为0,调用Request::instance()->param(),又到了熟悉的param()了,后面的跟上面一样。

将命令执行结果返回到$vars中。

为什么输出四个呢? 前两个为开启debug里面调用param()链,然后因为$values里面又两个’whoami’,所以遍历的时候执行了2次,后面的是module方法调用的param()链,后面也一样执行了两次,所以显示4个结果。

3.2 payload

POST http://127.0.0.1/tp5.0/public/index.php?s=index
_method=__construct&filter[]=system&method=GET&get[]=whoami

4 5.0.X 数据库信息泄露

关键点

也是路由过滤不严谨,导致调用任意类方法,这回只是利用链不一样了,调用think\Config中的get方法,读取配置文件。

4.1 原理分析

直奔关键代码部分,调用module()执行传入的方法。

跟进到module()中,到最后调用静态方法invokeMethod()执行操作。

跟进invokeMethod(),开始实例化反射类ReflectionFunction,传入$class和$method,也就是模块控制器方法,再用静态方法bindParams(),把参数绑定到$args中,也就是我们传入的”database.username”,最后调用invokeArgs将参数传入之前的反射类中执行。

因为执行的是get方法,跟进get()方法,此方法为读取配置文件,所以我们构造的参数进入get()中就会控制读取内容,后面返回self::$config[$range][$name[0]][$name[1]]。

也就是::config下的_sys_下的database下的username,也就是返回了数据库的用户名,我们构造的database.username,也可以换成database下的任意一个元素,database.password也就是查看数据库密码。

4.2 payload

http://127.0.0.1/tp5.0/public/index.php?s=index/thinkconfig/get&name=database.username

综合利用工具地址:

https://github.com/iceberg-N/thinkphp5.x_Scan


本文转载自: https://blog.csdn.net/m0_46689007/article/details/128119192
版权归原作者 iceberg-N 所有, 如有侵权,请联系我们删除。

“thinkphp5 漏洞原理分析合集”的评论:

还没有评论