做这篇文章主要是想熟悉在mvc模式下的cms或其他项目中进行代码审计。文字较多~
MVC模式
MVC是一个设计模式,它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型(M)、视图(V)、控制器(C),它们各自处理自己的任务。
模型 :
模型表示企业数据和业务规则。在MVC的三个部件中,模型拥有最多的处理任务。被模型返回的数据是中立的,就是说模型与数据格式无关,这样一个模型能为多个视图提供数据。由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。
控制器 :
控制器接受用户的输入并调用模型和视图去完成用户的需求。所以当单击Web页面中的超链接和发送HTML表单时,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后确定用哪个视图来显示模型处理返回的数据。
视图 :
视图是用户看到并与之交互的界面。对老式的Web应用程序来说,视图就是由HTML元素组成的界面,在新式的Web应用程序中,HTML依旧在视图中扮演着重要的角色,新技术不另讲,例Adobe Flash和XML/XSL。MVC一个大的好处是它能为你的应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,不管这些数据是联机存储的还是一个雇员列表,作为视图来讲,它只是作为一种输出数据并允许用户操纵的方式。
现在总结MVC的处理过程:
- 首先控制器接收用户的请求,并决定应该调用哪个模型来进行处理
- 模型用业务逻辑来处理用户的请求并返回数据
- 最后控制器用相应的视图格式化模型返回的数据
- 通过表示层呈现给用户。
PHPcms V9
目录结构
| – api 接口文件目录
| – caches 缓存文件目录
| – configs 系统配置文件目录
| – caches_* 系统缓存目录
| – phpcms phpcms 框架主目录
| – languages 框架语言包目录
| – libs 框架主类库、主函数库目录
| – model 框架数据库模型目录
| – modules 框架模块目录
| – templates 框架系统模板目录
| – phpsso_server phpsso 主目录
| – statics 系统附件包
| – css 系统css包
| – images 系统图片包
| – js 系统js包
| – uploadfile 网站附件目录
| – admin.php 后台管理入口
| – index.php 程序主入口
| – crossdomain.xml FLASH跨域传输文件
| – robots.txt 搜索引擎蜘蛛限制配置文件
| – favicon.ico 系统icon图标
URL访问
PHPcms是采用MVC设计模式开发,基于模块和操作的方式进行访问,采用单一入口模式进行项目部署和访问,无论访问任何一个模块或者功能,只有一个统一的入口。
模块访问方法 [示例] :
http://xxx.com/index.php?m=content&c=index&a=show&id=1
其中
m = content 为模型/模块名称 位于phpcms/modules/content
c = index 为控制器名称 位于phpcms/modules/content/index.php
a = show 为时间名称 位于phpcms/modules/content/index.php
中show()
方法
id = 1 为其他参数 与正常get传递参数形式相同
如果我们访问您的域名 如:
http://www.yourdomain.com/index.php
phpcms默认路由会定位到content模块的index控制器中的init操作,因为系统在没有指定模块和控制器的时候,会执行默认的模块和操作。因此下面的URL的结果是相同的:
http://www.yourdomain.com/index.php?m=content&c=index&a=init
系统还支持URL路由的功能,这些都能够带来其他的url访问效果。
入口程序
入口程序是在前期处理用户请求的引导程序,它是唯一一个可以被最终用户可以直接请求运行的。
phpcms v9的入口程序包含如下几行:
<?php
/**
* index.php PHPCMS 入口
*
* @copyright (C) 2005-2010 PHPCMS
* @license http://www.phpcms.cn/license/
* @lastmodify 2010-6-1
*/
//PHPCMS根目录
define('PHPCMS_PATH', dirname(__FILE__).DIRECTORY_SEPARATOR);
include PHPCMS_PATH.'/phpcms/base.php';
pc_base::creat_app();
?>
这段代码首先加载了 phpcms 框架的引导文件 base.php
,然后它根据指定的配置文件建立了一个 Web 应用实例并运行。
跟进一下base.php
这个load_sys_classs
静态方法,它加载了phpcms/libs/classes/application.class.php
,默认的这个函数这个方法是实例化的,所以我们找到application.class.php
,看看他的构造函数
application.class.php
<?php
/**
* application.class.php PHPCMS应用程序创建类
*
* @copyright (C) 2005-2010 PHPCMS
* @license http://www.phpcms.cn/license/
* @lastmodify 2010-6-7
*/
class 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();
}
/**
* 调用件事
*/
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.');
}
}
/**
* 加载控制器
* @param string $filename
* @param string $m
* @return obj
*/
private function load_controller($filename = '', $m = '') {
if (empty($filename)) $filename = ROUTE_C;
if (empty($m)) $m = ROUTE_M;
$filepath = PC_PATH.'modules'.DIRECTORY_SEPARATOR.$m.DIRECTORY_SEPARATOR.$filename.'.php';
if (file_exists($filepath)) {
$classname = $filename;
include $filepath;
if ($mypath = pc_base::my_path($filepath)) {
$classname = 'MY_'.$filename;
include $mypath;
}
if(class_exists($classname)){
return new $classname;
}else{
exit('Controller does not exist.');
}
} else {
exit('Controller does not exist.');
}
}
}
第15行,加载了param.class.php
,后面定义的常量都和param
有关,让我们来看看这个类文件
param.class.php
<?php
/**
* param.class.php 参数处理类
*
* @copyright (C) 2005-2012 PHPCMS
* @license http://www.phpcms.cn/license/
* @lastmodify 2012-9-17
*/
class param {
//路由配置
private $route_config = '';
public function __construct() {
if(!get_magic_quotes_gpc()) {
$_POST = new_addslashes($_POST);
$_GET = new_addslashes($_GET);
$_REQUEST = new_addslashes($_REQUEST);
$_COOKIE = new_addslashes($_COOKIE);
}
$this->route_config = pc_base::load_config('route', SITE_URL) ? pc_base::load_config('route', SITE_URL) : pc_base::load_config('route', 'default');
if(isset($this->route_config['data']['POST']) && is_array($this->route_config['data']['POST'])) {
foreach($this->route_config['data']['POST'] as $_key => $_value) {
if(!isset($_POST[$_key])) $_POST[$_key] = $_value;
}
}
if(isset($this->route_config['data']['GET']) && is_array($this->route_config['data']['GET'])) {
foreach($this->route_config['data']['GET'] as $_key => $_value) {
if(!isset($_GET[$_key])) $_GET[$_key] = $_value;
}
}
if(isset($_GET['page'])) {
$_GET['page'] = max(intval($_GET['page']),1);
$_GET['page'] = min($_GET['page'],1000000000);
}
return true;
}
/**
* 获取模型
*/
public function route_m() {
$m = isset($_GET['m']) && !empty($_GET['m']) ? $_GET['m'] : (isset($_POST['m']) && !empty($_POST['m']) ? $_POST['m'] : '');
$m = $this->safe_deal($m);
if (empty($m)) {
return $this->route_config['m'];
} else {
if(is_string($m)) return $m;
}
}
/**
* 获取控制器
*/
public function route_c() {
$c = isset($_GET['c']) && !empty($_GET['c']) ? $_GET['c'] : (isset($_POST['c']) && !empty($_POST['c']) ? $_POST['c'] : '');
$c = $this->safe_deal($c);
if (empty($c)) {
return $this->route_config['c'];
} else {
if(is_string($c)) return $c;
}
}
/**
* 获取事件
*/
public function route_a() {
$a = isset($_GET['a']) && !empty($_GET['a']) ? $_GET['a'] : (isset($_POST['a']) && !empty($_POST['a']) ? $_POST['a'] : '');
$a = $this->safe_deal($a);
if (empty($a)) {
return $this->route_config['a'];
} else {
if(is_string($a)) return $a;
}
}
/**
* 设置 cookie
* @param string $var 变量名
* @param string $value 变量值
* @param int $time 过期时间
*/
public static function set_cookie($var, $value = '', $time = 0) {
$time = $time > 0 ? $time : ($value == '' ? SYS_TIME - 3600 : 0);
$s = $_SERVER['SERVER_PORT'] == '443' ? 1 : 0;
$var = pc_base::load_config('system','cookie_pre').$var;
$_COOKIE[$var] = $value;
if (is_array($value)) {
foreach($value as $k=>$v) {
setcookie($var.'['.$k.']', sys_auth($v, 'ENCODE'), $time, pc_base::load_config('system','cookie_path'), pc_base::load_config('system','cookie_domain'), $s);
}
} else {
setcookie($var, sys_auth($value, 'ENCODE'), $time, pc_base::load_config('system','cookie_path'), pc_base::load_config('system','cookie_domain'), $s);
}
}
/**
* 获取通过 set_cookie 设置的 cookie 变量
* @param string $var 变量名
* @param string $default 默认值
* @return mixed 成功则返回cookie 值,否则返回 false
*/
public static function get_cookie($var, $default = '') {
$var = pc_base::load_config('system','cookie_pre').$var;
$value = isset($_COOKIE[$var]) ? sys_auth($_COOKIE[$var], 'DECODE') : $default;
if(in_array($var,array('_userid','userid','siteid','_groupid','_roleid'))) {
$value = intval($value);
} elseif(in_array($var,array('_username','username','_nickname','admin_username','sys_lang'))) { // site_model auth
$value = safe_replace($value);
}
return $value;
}
/**
* 安全处理函数
* 处理m,a,c
*/
private function safe_deal($str) {
return str_replace(array('/', '.'), '', $str);
}
}
?>
先声明一个私有变量 $route_config
,下面的构造函数get_magin_quotes_gpc()
是检测是否开启这个配置,若未开启,均对以上变量进行new_addslashes()
函数的过滤。 后3个函数就比较简单,返回Array( [m] => content [c] => index [a] => init)
里面的数据。
OK,我们返回application.class.php
26行,直接调用了load_controller
,我们直接看load_controller
函数
44行,load_controller($filename='',$m='')
上面的init
并没有传参,而且在构造函数中我们已经取出了ROUTR_C
,ROUTE_M
的值,那么此时的$filename=index,$m=content
所以 47–61行的代码意思为判断 这个模块下的这个文件是否存在,存在就加载,同样判断了有没有以MY_开头的自己的扩展,有的话也加载,并实例化,如果没有 就exit(‘Controller does not exist.’);
回到init函数,此时已经加载了控制器,并实例化了index.php
模块
phpcms v9框架中的模块,位于phpcms/modules
目录中 每一个目录称之为一个模块。即url访问中的m
变量
示例: http://www.yourname.com/index.php?m=content
那么访问的就是phpcms/modules/content
这个模块。
控制器
phpcms v9的控制器就是模块的类文件,位于phpcms/modules/模块/
目录下面。类名称就是文件名+.php
,例如一个名为mytest
的控制器,那么他的命名为mytest.php
即可。控制器类默认继承系统的函数库,可以直接使用。控制器类的类名称与控制器文件名必须相同。
如果您创建了一个mytest.php在test模块下,那么我们在浏览器里面输入URL:
http://www.yourname.com/index.php?m=test&c=mytest
。。。等等
有了以上的理解,在对mvc模式下的phpcms代码审计中能更加清晰了
GOT IT!
******************************************************
具体利用方式需根据具体实践场景~