Notice

1.文章基于phpcmsv9.5.10进行审计
2.文章的内容都来自 https://mp.weixin.qq.com/s/Yi4-0Z0C1GDtyaJAeCB1wQ 我只是记一个自己的笔记,推荐读者观看原文章

通读全文审计方法

MVC框架

在原文中提到了MVC框架这一开发模式,百度百科的官方解释,M是指业务模型(Model)V是指用户界面(view)C则是控制器(controller),使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。其中,View的定义比较清晰,就是用户界面。
可以这么理解,当用户发起一个请求的时候 controller获取用户输入,调用model,将数据交给view来展示
那么为什么我们要使用MVC框架?,这里提到一个名词 耦合性
耦合性指的是模块和模块之间的依赖程度,简单来说我们的业务代码肯定不会只一个版本,需要不断的更新,而某次更新如果我们只需要更改业务中的一个小模块,我们希望看到的肯定是直接更改完就好,但是如果模块模块依赖太严重,就可能需要修改其他模块,这肯定不是我们所想要的
知道什么是MVC框架,我们也要了解一下他的利弊,优点是耦合性低,分工合作性强,重用性也很高,缺点就是我们要很了解系统的架构才能进行开发

如何审计

基于mvc模式开发的cms,会有一个统一的入口,所有的请求都从该入口进入,然后由框架统一进行调度,且程序中的一些可重用的操作,例如数据库操作、缓存、安全过滤等都会被封装成框架的核心类库,需要时再去调框架封装好的方法。如果在没了解程序的结构前,就贸然去跟某段代码,很容易迷失在一个个类库的调用链里。

所以通读全文代码了解大概架构才尤其重要,我们要起手的就是框架运行流程
B6869A45-5EC4-48CE-A024-CB338960AB8D.png
找入口的文件,但实际开发中有很多个入口文件,前后端接口等等,入口文件的作用如图中所表达的,要定义一些全局常量,加载函数库,加载框架启动类等等,这里面比较值得提到的点就是类自动加载,通常会将类文件的加载操作封装成一个函数或者方法,然后在框架启动前或者在框架启动时,通过spl_autoload_register将类加载函数注册到SPL__autoload函数队列中,当程序内的代码调用某个类时,php会调用类加载函数,类加载函数就会到正确的地方去把类库文件加载进来;当然也可以不使用spl_autoload_register函数来自动调用,而是在需要时手动去调用。再下一步,就是启动框架。框架启动后,可能会去加载一些配置文件,然后去调用路由类来解析请求url,根据解析的结果来调用相应的控制器,最后返回结果。

实际分析

实际分析,一般入口是根目录下的index.php 和api.php admin.php 一般前台的入口文件都是index.php

define('PHPCMS_PATH', dirname(__FILE__).DIRECTORY_SEPARATOR);
include PHPCMS_PATH.'/phpcms/base.php';
pc_base::creat_app();

可以看到很简单的定义一个常量,包含文件,调用pc_base类的creat_app()方法
我们跟进这个类 也就是base.php
类里面define了各种各样的常量,还加载了一些函数库,这应该就是框架启动类了
继续跟进creat_app()这个方法

public static function creat_app() {
    return self::load_sys_class('application');
}

继续跟进load_sys_class方法

public static function load_sys_class($classname, $path = '', $initialize = 1) {
        return self::_load_class($classname, $path, $initialize);
}

继续跟进load_class

private static function _load_class($classname, $path = '', $initialize = 1) {
        static $classes = array();
        if (empty($path)) $path = 'libs'.DIRECTORY_SEPARATOR.'classes';

        $key = md5($path.$classname);
        if (isset($classes[$key])) {
            if (!empty($classes[$key])) {
                return $classes[$key];
            } else {
                return true;
            }
        }
        if (file_exists(PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php')) {
            include PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php';
            $name = $classname;
            if ($my_path = self::my_path(PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php')) {
                include $my_path;
                $name = 'MY_'.$classname;
            }
            if ($initialize) {
                $classes[$key] = new $name;
            } else {
                $classes[$key] = true;
            }
            return $classes[$key];
        } else {
            return false;
        }
    }

这里面就包含了加载类的主要方法,因为原文章的内容清晰易懂,我就直接放上来
从代码中可得知,类库目录在phpcms框架目录下的$path目录,也就是/phpcms目录下,类文件名为“类名.class.php”,如果$path参数为空,则默认到/phpcms/libs/classes目录下去加载类文件,然后根据$initialize参数来决定是否实例化该类。前面的load_sys_class方法在调用load_class时,传入的$path参数是空的,且$initialize=1,也就是说,/phpcms/libs/classes目录下放的是系统类,且加载系统类库时会自动实例化该类。除了load_sys_class,还有load_app_class和load_model,三者的底层都是调用了_load_class,区别在于传入的$path不同。
B4A45652-F0E0-4333-9AAB-DE690C671EC1.png
然后通过这个就知道大概的启动类的过程,但我们框架启动成功之后就是要路由解析了,回到开头的index.php,调用creatapp()函数加载了application的类

public function __construct() {
        $param = pc_base::load_sys_class('param');
        define('ROUTE_M', $param->route_m());
        define('ROUTE_C', $param->route_c());
        define('ROUTE_A', $param->route_a());
        $this->init();
    }

我们知道下一步要进行路由解析,所以param肯定是路由解析相关的类
构造函数里面先对传参进行一些过滤 路由解析完后,再下一步就是加载控制器。
然后就是调用route_m、route_c、route_a方法解析路由,获取模块、控制器和方法名。

public function __construct() {
    $param = pc_base::load_sys_class('param');
    define('ROUTE_M', $param->route_m());
    define('ROUTE_C', $param->route_c());
    define('ROUTE_A', $param->route_a());
    $this->init();
}

跟进init函数

private function init() {
        $controller = $this->load_controller();
        if (method_exists($controller, ROUTE_A)) {
            if (preg_match('/^[_]/i', ROUTE_A)) {
                exit('You are visiting the action is to protect the private action');
            } else {
                call_user_func(array($controller, ROUTE_A));
            }
        } else {
            exit('Action does not exist.');
        }
    }

跟进load_controller方法。
load_controller方法先到“/phpcms/modules/模块名/”目录下加载控制器文件,然后实例化并返回对象。

init方法通过load_controller方法获取到控制器实例后,通过call_user_func方法来执行具体的动作,最后返回结果,请求结束。