php基础不好,分析下thinkphp的入口代码,后面将分析thinkphp反序列化漏洞和命令执行。
一、环境
软件 | 环境 |
---|---|
phpstudy | 8.1.0.1 |
thinkphp | V5.1.35 LTS |
nginx | 1.15.11 |
php | 7.3.4 |
二、入口文件
首先分析入口文件:public/index.php
。
1 |
|
跟进 base.php
文件。
1 | namespace think; |
其中 Loader::register()
是用来配置类的自动加载。
2.1、类的自动加载
在php中,如果想使用其他文件中的类,必须先引用这个文件。例如,我在 test3.php
中定义一个类。
1 |
|
然后在test4.php中调用这个类
1 |
|
这时运行会产生错误。
PHP Fatal error: Uncaught Error: Class ‘test\Test’ not found in /E
但如果我使用了类的自动加载。将不再会发生错误。
1 |
|
然后重新访问,一切正常。当我初始化一个类时,如果没有找到这个类,就会调用类的自动加载。
2.2、thinkphp类自动加载
在 Loader::register();
中将调用下面内容:
1 | public static function register($autoload = '') |
通过 think\\Loader::autoload
函数进行自动加载。
2.3、调试
php发现没有找到Container这个类,所以会调用 think\\Loader::autoload
函数。在下面图中可以看出,在这个函数中将会 include
Container
这个类。
三、run函数
thinkphp会执行 Container::get('app')->run()->send();
在Container中确定 app
为 thinkphp/library/think/App.php
文件。跟进该类的 run
函数。
首先是进行一系列的初始化相关操作。
1 | $this->initialize(); |
然后设置一些列的hook点。这里的hook函数可以理解为一种 AOP编程。如果添加了监听的函数,当执行到hook点时,将会自动调用监听的函数。
1 | $this->hook->listen('app_init') |
接着绑定路由,如下,默认情况下这两个判断都是 false
。
1 | if ($this->bindModule) { |
然后是进行路由选择,即根据我们的情求找到正确的Controller。
1 | // 监听app_dispatch |
最后就是进行调度。获取response。
1 | $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) { |
四、路由访问
thinkphp访问一个文件存在以下几种方式。
- http://127.0.0.1/index.php(入口文件)/模块/控制器/操作/[参数名/参数值…]
- http://127.0.0.1/index.php(入口文件)?s=/模块/控制器/操作/[参数名/参数值…]
- http://127.0.0.1/index.php?m=module&c=controller&a=action&var1=vaule1&var2=vaule2 (thinkphp5不再支持这种访问方式)
4.1、路由过程
在 APP.php
中,首先thinkphp会检测路由。依据访问的URL解析出对应的模块、控制器和动作。
1 | // 监听app_dispatch |
routeCheck()
调用本文件中的 routeCheck()
方法,URL路由检测(根据PATH_INFO)。在 routeCheck()
方法中,有两个重要的方法需要关注,一个是 $this->request->path()
用于获取当前请求URL的pathinfo信息(不含URL后缀) ,另一个是 $this->route->check
检测这个路由是否有效。
path()方法
然后进入 pathinfo()
,会在这里判断输入的路由格式,即判断是哪种访问方式。在thinkphp5.1中,可以使用两种方式,一种是 入口文件/模块/控制器/action
。另一种就是 入口文件?s=/模块/控制器/action
。
回到 routeCheck()
方法中。上面的 path()
返回了路由信息。然后调用 check()
方法。该方法会返回一个对象。
这里返回了一个 UrlDispatch
对象。这个对象存在 init
方法。后面会被调用。
然后返回到 APP.php
的 run
方法中。其中 routeCheck()
会返回上面的 UrlDispatch
对象。然后调用该对象的 init
方法。
1 | $dispatch = $this->routeCheck()->init(); |
init()方法
$this→parseUrl()方法
这个函数用来解析URL地址。就是根据传入的URL参数来找到模块、控制器和操作。
返回一个封装的路由信息。
返回到 init
函数中,下图为解析后的内容。最后返回一个 Module
对象。
- 这个
Module
对象的init方法也是返回Module
对象。 Module
对象继承了Dispatch
对象。Dispatch
对象存在run
方法。后面会被用到。
接下来就是后面的路由调度。
4.2、路由调度
下图有两个步骤。
其中 add()
是添加一个中间件。这个中间件就是传入的匿名函数。 $data
是在出现异常时返回的内容。如果没有出现异常,中间件将会执行 $dispatch->run()
函数。上面提到过,Module
对象继承了 Dispatch
对象。 Dispatch
对象存在 run
方法。
在下面的 $this->middleware->dispatch()
为调用中间件。即执行上面的 $dispatch->run()
函数。跟进这个函数后,继续跟进 exec()
。注意,这里的主体对象仍然是 Module
对象。
在 exec()
函数中,会找到控制器、操作等。然后通过 invokeReflectMethod
方法进行调用。
下图是我访问这个地址时的调用: http://127.0.0.1/public/index.php/index/index/hello?a=1
经过对控制器调用结束后,将会执行到下面的内容。在 $this->exec() 中会调用第一次 autoResponse 函数,然后将内容变成 Response 对象。然后在下面的代码中将第二次调用 autoResponse 。这里进行验证,判断当前返回是否为 Response 对象。
五、Response的send方法
其中send方法主要就是将数据发送到客户端。