thinkphp 源码分析系列(二)—— 路由

4 篇文章 0 订阅
2 篇文章 0 订阅

0x00 前言

这篇文章主要是结合 thinkphp 5.0.x 两个rce :
(1)变量覆盖filter
(2)没有开启强制路由导致rce
来分析thinkphp 的路由

0x01 路由检测

首先要说的是$dispatch 调度信息,
类似于:
在这里插入图片描述

调度信息最后会传入App::exec() 中:
在这里插入图片描述
在这里插入图片描述
而exec 会根据调度信息的type ,去调用相应的函数去映射到具体的函数上去执行。
在这里插入图片描述

App.php 中run() 函数中,$dispatch 是通过 App.php 中的routeCheck() 函数返回的
在这里插入图片描述
App.php 中的routeCheck() 函数先得到path:

在这里插入图片描述
然后看config 中参数是否开启路由【默认都是开启的】,如果开启的,先看是否有路由缓存文件,没有的话,直接解析route.php 文件。[congfig 中rout_config_file 配置的]
在这里插入图片描述-------
关于从路由配置文件route.php 中导入路由配置并且解析:
路由配置文件route.php 中允许两种方式,一种是直接 route::get(…) ,另一种是return 规则数组。
直接route::get() 的话就通过上面的Include 包含进来 ,但是route::get 也会走后面的Route::setRule : get() -> rule -> setRule ,到setRule 的时候type 就直接是GET了,而不是*
在这里插入图片描述
Route::import 是解析route.php 中return 的规则数组
关于Route::import
在这里插入图片描述最后会调用 Route::registerRules 来注册路由 ; 当然里面还会先注册域名部署,变量规则,路由别名,资源路由。
在这里插入图片描述
通过registerRules 进入setRule 的时候,type 都是 *

在这里插入图片描述
registerRules 中会根据route.php 中的格式【对应的就是rules 数组】来进行路由注册,比如分组路由,直接setRule() 路由等等。

再跟进Route::setRule ,作用就是赋值Route::$rules 变量,结果类似于: 在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
另外setRule 中还解析变量
在这里插入图片描述

另外可以看到328-336行,通过type 为 * 注册的(也就是在route.php 中通过return 注册的,都会在Route::$rules 注册上所有的方法(也就是快捷方式))
但真正的方法注册到的是option 中:

在这里插入图片描述

会调用Rout::check 函数进行路由检测:
App::routCheck()先导入路由配置,调用的是Route::import
在这里插入图片描述
然后调用Route::check 来进行路由检测,第二个参数是path 信息,【对应check 形参中的 $url 】
在这里插入图片描述
Route.php 中Route::check() 函数中先判断静态路由:
在这里插入图片描述
不是静态路由的话,调用checkRoute检查路由是否匹配规则
在这里插入图片描述

判断请求方法,然后通过方法赋值$rules 变量在这里插入图片描述
在这里插入图片描述
(这也是变量覆盖那个rce 的触发点!!!🚩)

---- 1-------
变量覆盖那个rce 问题就出在\ $requet->method() 里面:
route检测的时候,为了获取请求方法,以进行路由匹配,调用Request::method(), 但是Request::method() 直接从$POST 中直接获取__method ,然后还会调用$this->{this->method}($_POST) ,这里如果传入__method为__construct() 就存在变量覆盖了。
在这里插入图片描述

其实method()这样做的原意是为了通过表单传入put ,delete 等方法,顺便获得put ,delete 的请求参数
在这里插入图片描述
但是没有对传入的’var_method’(__method)进行检测,所以就可以动态调用__construct() :
在这里插入图片描述
那么就存在变量覆盖了,可以覆盖掉Request:: 中的Request::$filter ,Request::$get 参数

POST http://localhost/tp/public/index.php?s=captcha
_method=__construct&filter[]=system&method=GET&get[]=whoami

-----1-------

然后调用Route::checkRoute() 检查路由是否匹配规则

在这里插入图片描述
checkRoute会依次遍历rules,对每个rule 进行检验:【可以看到911行就是判断是否是快捷路由,如果是的话通过Route::getRouteExpress 得到具体的 $item 】:
在这里插入图片描述

在这里插入图片描述

然后判断是否是分组路由,是分组路由再重新调用Route::checkRoute,不是的话直接rule调用checkRule 检测url 路由
在这里插入图片描述
Route::checkRule:
在这里插入图片描述
会调用Route::match() 去检测url 和路由规则是否匹配:
在这里插入图片描述
Rout::match() 函数 ,其实利用的就是正则匹配
在这里插入图片描述
如果匹配到了,会调用Rout::parseRule() 方法,把路由解析到具体的方法 ;这个方法的rule 就是前面Rout::match() 函数匹配到的路由的rule ,比如 :在这里插入图片描述
在这里插入图片描述
自此,就返回了$dispatch 调度信息为某个模块的某个方法(type 见上图的parseRule 中的type 赋值,有上面5种type)
在这里插入图片描述
在这里插入图片描述

=> 对应的就是\thinkphp\thikphp5.0.22\vendor\topthink\think-captcha\src\CaptchaController.php
在这里插入图片描述
如果没有匹配到,会检查是否开启了强制路由,如果开启了则抛出路由无效的异常, 🚩
如果没有开启强制路由,则会调用Route::parseUrl() 进行控制器自动搜索
在这里插入图片描述
在这里插入图片描述
-----2-------
对于thinkphp 没有开启强制路由的rce [问题出在 App::module 没有对控制器名进行过滤,然后会调用Loader::controller 的时候就导致可以加载类似 think\app 这种controller(也就是这个app 类) ]

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

关键点在于 App::routeCheck 中:
在这里插入图片描述
如果没有匹配到路由,会检查是否开启了强制路由,如果开启了则抛出路由无效的异常,
如果没有开启强制路由,则会调用Route::parseUrl() 进行控制器自动搜索

Route::parseUrl :会返回 [‘module’,‘route’(array)(重新封装好的路由)]给调度器在这里插入图片描述
在这里插入图片描述
parseUrl中会先调用Route::parseUrlPath 进行 path解析,来解析URL的pathinfo 参数和变量
在这里插入图片描述
然后会解析直接利用array_shift() 函数一一解析模块名,控制器名和方法名(也就是操作名):
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后没有开启debug 模式的时候,会调用App::exec() ,App:exec() 中会根据不同的调度类型,选择执行的函数,比如这里是module 类型,就会调用App::module() 方法

在这里插入图片描述

App::module() 方法 ,用来执行模块:
在这里插入图片描述
进行模块的一些配置初始化操作:
在这里插入图片描述
比如里面的模块初始化App::init($module),就是加载一些模块配置,数据库配置等等:
在这里插入图片描述
然后设置请求的控制器和操作并且监听 module_init:
在这里插入图片描述
然后会实例化控制器: 使用的是 Loader::controller() 方法[问题就出在这]
在这里插入图片描述
在这里插入图片描述

然后设置操作名:
在这里插入图片描述
然后设置监听,同时调用App::invokeMethode() 来调用方法
在这里插入图片描述
invokeMethod() 里面会进行参数绑定,调用的是bindParams() 函数:
在这里插入图片描述
对于bindParams():有趣的是他在invokeMethod 之前也没有进行参数绑定,而是在invokeMethod的时候,才进行参数绑定。
在这里插入图片描述
(invokeMethod 的时候就没有传入参数)
在这里插入图片描述
【这里面调用了Requests::param() 方法】
【这里面调用了Requests::param() 方法】
在这里插入图片描述
成功调用:
在这里插入图片描述

0x03 其他: 关于参数绑定

既然上面谈到了参数绑定,所以决定再细说一下thinkphp 中参数绑定:
在这里插入图片描述
有两种参数传递方式: 一个是 url_parm_type 为 1 ,表示在这里插入图片描述
按照解析顺序进行,另一种是0(默认),按照键值对解析。
374行到375行,先获取请求的变量,放到 v a r s 数 组 中 : 然 后 先 判 断 调 用 的 函 数 参 数 的 调 用 个 数 是 不 是 大 于 0 , 是 的 话 继 续 进 行 , 否 则 返 回 参 数 为 [ ] 然 后 251 行 , 得 到 函 数 的 反 射 参 数 数 组 然 后 253 行 , 得 到 函 数 的 反 射 参 数 名 , 然 后 263 行 如 果 是 u r l p a r a m t y p e 为 1 , 则 判 断 vars 数组中: 然后先判断调用的函数参数的调用个数是不是大于0,是的话继续进行,否则返回参数为[] 然后251行,得到函数的反射参数数组 然后253行,得到函数的反射参数名,然后263行如果是url_param_type 为1 ,则判断 vars:0[]251253263urlparamtype1var 数组是否为空,不是空就赋值参数名;如果url_param_type 为0 ,则265行判断参数名是不是在$vars 数组中,如果在就赋值
$this->param() 中: $this->get(): 注意在路由匹配的时候unset 了 KaTeX parse error: Expected 'EOF', got '#' at position 248: …lor_FFFFFF,t_70#̲pic_center) ![在…args 数组,所以也可以看得出是顺序传入的(是数字形下标数组) ,比如形参有a,b,c ,如果只传入 a ,c 的话,args 中的结果也就是a,c 的值,所以这一步可能就会调用出错。

在这里插入图片描述

另外254行到261行,对于参数是类的话也可以进行解析(这个还有待研究,不知道怎么传类的),那么键值对的key 就是class ,value 就是 class
最后那个全局过滤好像并没有什么用: 就是在过滤的字符串后面加上一个空格:
在这里插入图片描述

0x04 变量覆盖的那个rce ,为什么需要captcha 这个路由

对于变量覆盖的那个rce ,既然在 路由检测的时候就以及覆盖掉了 filter 和 get,那为什么还是需要captcha 这个路由呢?

我们尝试直接这样打;
在这里插入图片描述
造成这个payload 不行的原因这样请求,$dispatch 的 type 会是module,type 为module 的话,在App::exec() 中会调用App::module() ,而在App::module() 方法中的544 行:

在这里插入图片描述
在这里插入图片描述
在这一步之前,我们可以看到filter 是成功覆盖成了 system 。
但是经过544 行,我们跟进:
在这里插入图片描述
可以看到这里会直接把$requests->filter 重新设置为 default_filter 也就是null:
在这里插入图片描述

而对应的captcha 的路由,我们看到在 thinkphp5.0.22\vendor\topthink\think-captcha\src\helper.php中:
在这里插入图片描述
在这里插入图片描述
可以看到这样使用 @ 定义路由的话,返回的dispath 中的 type 是method ,
而type 为method 的时候,469 行会先调用$request->param()
在这里插入图片描述
会先调用 $request->param() 方法, 而在这个方法中661行,会调用 $request->input() 方法
在这里插入图片描述
而我们知道input方法中 会对传入的第一个参数(data) 用 filter 来处理:
在这里插入图片描述
这样就造成了rce 。
当然type 为 controller 的时候也可以通过这样来rce:
在这里插入图片描述

所以只要存在 type 为 method 或者为 controller 的路由,就可以通过这样来rce 。

0x05 关于变量覆盖rce payload 的一些问题

POST http://localhost/tp/public/index.php?s=captcha

_method=__construct&filter[]=system&method=GET&get[]=whoami
(1) 需不需要 method= GET:

i . 先看5.0.0 版本:
在这里插入图片描述
在这里插入图片描述
可以看到如果没有传入method=GET 的话,在814行会执行:

$rules = self::$rules[$method];

而这个时候的$method = “__construct” ,所以就会报一个未定义索引的错误,导致退出。

ii. 再看 5.0.22 版本

在 Route.php 中的857 行和 858行 :
在这里插入图片描述

在这里插入图片描述

修改为了 :

$rules = isset(self::$rules[$method]) ? self::$rules[$method]:[];

这样就不会报错,程序继续正常运行。但是这样$rules 就会变为 [] , 会直接再后面Route::check() 直接返回 false,而不进入self::checkRoute()
在这里插入图片描述

所以最后App::routCheck() 中会走Route::parseUrl
在这里插入图片描述
这样返回的$dispatch的type 就会变为 module ,而之前也分析了,走module的话,会重新设置 filter 为默认的filter ,这样payload 就打不了了。

而且如果换成 method = POST 或者 PUT 也是不行的,因为captch 的路由规定了是get 路由:
在这里插入图片描述

所以总结来说是需要 method = GET 。

(2) 为什么payload 会执行两次:

在这里插入图片描述

因为$this->param()会把 $_POST 和 $this->get() 融合一次,所以filter 的时候data 就是:
在这里插入图片描述
array_walk_recursive() 会递归到深层的数组中去:
在这里插入图片描述

所以array_walk_recursive() 的时候就会调用两次 system(‘whoami’);
在这里插入图片描述

像这个payload 就只会执行一次 system(whoami)

_method=__construct&filter[]=system&method=GET&a=whoami

在这里插入图片描述

所以总结来说 get[]=whoami 这里的get[] 不是必须的,我们可以改为其他的变量。payload 也能正常的打。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值