关于模块部署的一个漏洞分析
在使用模块部署的时候,遇见了这样的一个问题:
近期公司有一个商城项目,技术选型用到了ThinkPHP框架,使用的是3.2版本,有一个这样的需求:
项目总共分为两个模块,分别是前台Home和后台Admin,很常规的配置。这时在访问的时候希望前台可以隐藏模块名,后台访问的模块名改为manager,根据文档说可以这样配置:
'MODULE_ALLOW_LIST' => array('Home','Manager'),
'DEFAULT_MODULE' => 'Home',
'URL_MODULE_MAP' => array('manager'=>'admin'),
注意:以上配置必须在应用配置里面设置的,不能在模块配置。原因可以通过ThinkPHP的系统流程来看:它会在27步进行url调度,然后在33步加载模块配置,而url调度时就会用到那些配置,如果在模块中设置则会读取框架默认的配置文件,就导致设置无效。
这样设置后了,访问后台模块/manager/dashboard/show是没有任何问题的(已经在配置文件中忽略url的大小写),根据文档访问前台比如HomeController的index方法可以这样:/home/index,结果却是提示Index控制器不存在;假如我前台还有一个控制器是UserController,访问其中的index方法则会提示User模块不存在。这是为什么呢?
实际上,要通过配置来隐藏模块名,还必须在配置文件中配置一项
MULTI_MODULE为false才可以。看了源码才会明白,文档不会和你说的,但是这样配置了就会导致模块名映射失效的。
ThinkPHP的url调度是在Think\Dispatcher::dispatch方法中的,我们可以通过IDE的断点跟进来查看,该方法的代码非常多,我们选择三段主要的来分析:
以上这段代码就是解析模块名的,在138行判断__INFO__常量和MULTI_MODULE是否为真,这个__INFO__就是我们访问的url,而MULTI_MODULE表示是否开启多模块配置,默认为开启。在默认情况下会往$_GET这个数组里面写入模块名,然后会在这里定义当前模块名:
根据以上代码可以知道,如果我们有定义BIND_MODULE的话当前模块就是该常量值,而如果没有定义的话就是会从getModule()这个方法来获取,我们再跟进看看这个代码:
框架会先从$_GET中来获取来获取模块名,而默认情况下MULTI_MODULE是开启的,所以$_GET中就会有当前模块名(实际该模块名是错误的),在354行就会获取到当前模块名为Service(这个实际是控制器名),然后去配置中读取允许的模块名做判断后发现Service不存在,抛出模块不存在异常。
以上是在MULTI_MODULE开启的情况下(该配置框架默认开启),假如这个配置关闭呢?
根据文档该配置关闭后,就必须配置DEFAULT_MODULE这个选项,我们配置为了Home。到这里相信大家已经清楚了,关闭这个后前台是可以隐藏入口来访问的,但是后台就不能通过映射来访问了。因为在138行的代码写着很清楚了,关闭后框架就不会从url中来解析当前模块名,而是获取配置的DEFAULT_MODULE的值,来作为当前模块名。顺便一起分析下把:
当MULTI_MODULE关闭的时候,$_GET中就不会有当前的模块名了,所以框架就获取DEFAULT_MODULE来作为当前模块名,实际这样是不对的,因为在有设置模块映射的情况下,这样子是获取不到当前访问真正的模块的。然后在361行会判断获取的模块是否有在URL_MODULE_MAP中,不过既然模块都获取错了,那肯定是不会在这里的,所以就返回了DEFAULT_MODULE的配置值。
总结一下,在使用模块部署的时候:
- 若想要隐藏模块名必须配置MULTI_MODULE为false,这个文档中是没有写的。
- 如果想要做模块映射,那么这个配置就必须为true。
这两个东西不能共存的,虽然文档写着可以。不知道该问题是我分析有误,还是作者有意而为之。
完。
这个问题我也有遇到,折腾了一个中午,后来换成早时间下载的3.2.3版本问题就没有出现,但是直接从官网下载3.2.3版本就会出现这个问题,很是费解
这个用法应该挺普遍的,严重关注这个问题。
官方也不给出一个回复,没办法,以后会在项目中少用tp框架。 不仅如此,tp框架的系统appinit行为在调用控制器方法的时候也会出现死循环的bug,目前无解
碰到这种问题也是挺老火的,特别作为初学者更是丈二和尚摸不到头脑,不过作为开源项目,有能力的可以自己提一个PR
目前官方的主要精力不在3.2上 所以3.2的更新和修正速度可能会有一定的影响,原则上也不会新增功能和优化改进了。3.2的隐藏模块名的设计本身就存在缺陷,因此5.0抛弃了这种思路,推荐的方式是模块别名和路由。