PHP代码审计入门篇二 ——MVC结构审计
2024-10-14 08:32:30

0x0 MVC简介

在审计代码之前我们先来了解下 什么是MVC。

MVC模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发。

  • 模型Model – 管理大部分的业务逻辑所有的数据库逻辑。模型提供了连接和操作数据库的抽象层。
  • 控制器Controller - 负责响应用户请求、准备数据,以及决定如何展示数据。
  • 视图View – 负责渲染数据,通过HTML方式呈现给用户。

mvc的运行流程:

  1. controller获取用户的请求
  2. controller根据获取的请求调用相应的Model完成状态的读写操作
  3. controller将Model处理的数据传递给View
  4. View将数据渲染将结果呈现给用户

0x1 了解目录结构

1
2
3
4
5
6
7
8
9
10
11
12
-- YxtCMF_v6.1
-- admin 后台静态文件
-- application 应用目录
-- data 数据配置文件
-- Expand 静态储存扩展
-- plugins 插件
-- public 资源文件目录
-- themes 主题
-- ueditor 编辑器
-- update 升级目录
-- Uploads 上传目录
-- yxtedu 核心目录根据Thinkphp3.2.3开发的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
├─ThinkPHP 框架系统目录(可以部署在非web目录下面)
│ ├─Common 核心公共函数目录
│ ├─Conf 核心配置目录
│ ├─Lang 核心语言包目录
│ ├─Library 框架类库目录
│ │ ├─Think 核心Think类库包目录
│ │ ├─Behavior 行为类库目录
│ │ ├─Org Org类库包目录
│ │ ├─Vendor 第三方类库目录
│ │ ├─ ... 更多类库目录
│ ├─Mode 框架应用模式目录
│ ├─Tpl 系统模板目录
│ ├─LICENSE.txt 框架授权协议文件
│ ├─logo.png 框架LOGO文件
│ ├─README.txt 框架README文件
│ └─ThinkPHP.php 框架入口文件

mvc架构的cms通常会有URL路由

例如我们访问http://serverName/index.php/login

等同于访问http://serverName/index.php/user/login/index

对应的controller类是:application\User\Controller\IndexController.class.php

了解相应的路由规则可以有效的定位漏洞的触发点

0x2 入口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
if (ini_get('magic_quotes_gpc'))
{
function stripslashesRecursive(array $array)
{
foreach ($array as $k => $v)
{
if (is_string($v))
{
$array[$k] = stripslashes($v);
} else
if (is_array($v))
{
$array[$k] = stripslashesRecursive($v);
}
}
return $array;
}
$_GET = stripslashesRecursive($_GET);
$_POST = stripslashesRecursive($_POST);
}
define("APP_DEBUG",false);
define('SITE_PATH', dirname(__file__) . "/");
define('APP_PATH', SITE_PATH . 'application/');
define('SPAPP_PATH', SITE_PATH . 'yxtedu/');
define('SPAPP', './application/');
define('SPSTATIC', SITE_PATH . 'statics/');
define("RUNTIME_PATH", SITE_PATH . "data/runtime/");
define("HTML_PATH", SITE_PATH . "data/runtime/Html/");
define("THINKCMF_CORE_TAGLIBS", 'cx,Common\Lib\Taglib\TagLibSpadmin,Common\Lib\Taglib\TagLibHome');
if (!file_exists("data/install.lock"))
{
if (strtolower($_GET['g']) != "install")
{
header("Location:./index.php?g=install");
exit();
}
}
require SPAPP_PATH . 'Core/ThinkPHP.php';

line 2-21判断了是否开启了magic_quotes_gpc ,如果开启了则会对GET、POST获得的数据进行处理。处理使用的函数就是stripslashes返回一个去除转义反斜线后的字符串(\' 转换为 ' 等等)。双反斜线(\\)被转换为单个反斜线(\)。

此处感觉多此一举,如果是开启了magic_quotes_gpc,程序直接使用了

$_GET、$_POST传输的数据则会存在安全问题。

下面的流程就是引入ThinkPHP的核心文件和判断是否已经安装

0x3 审计

因为此程序采用的mvc模式 所以 审计我们只要把重点放在controller这里即可

有些读者可能不知该如何寻找出漏洞,在拿到源码后感觉无从下手。

笔者在这里分享下自己的几个方法以供参考

  • 使用Seay源码审计根据特征扫描出来的位置进行跟进审计
  • 通读整个源码的流程层层跟进审计(这种需要耗费大量时间,但时间与收益是对等的,遗漏的东西不多)
  • 黑盒+白盒共同测试,根据前台或后台页面位置的功能,有选择的审计某个功能所对应的源码。例如:sql注入一般是需要有输入的地方,那么可以找登录模块、搜索模块、文章模块等等

选择合适的方法可以为你接下来进行的工作提高效率。

0x3.1 前台登录一处注入

application\User\Controller\LoginController.class.php

LIne 116-140

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//登录验证
function dologin(){
if(!sp_check_verify_code()){
$this->error("验证码错误!");
}
$users_model=M("Users");
$rules = array(
//array(验证字段,验证规则,错误提示,验证条件,附加规则,验证时间)
array('username', 'require', '手机号/邮箱/用户名不能为空!', 1 ),
array('password','require','密码不能为空!',1),

);
if($users_model->validate($rules)->create()===false){
$this->error($users_model->getError());
}

$username=$_POST['username'];

if(preg_match('/^\d+$/', $username)){//手机号登录
$this->_do_mobile_login();
}else{
$this->_do_email_login(); // 用户名或者邮箱登录
}

}
  1. 首先验证了验证码是否正确(默认是没有开验证码功能的)
  2. 验证输入的内容是否满足 $rules的条件
  3. 132行直接接收了 $_POST传入的username
  4. 判断是以那种方式,手机号或者用户名登录的

问题出在了

因为是直接接受的Post传过来的内容,在Thinkphp 3.2.3where处存在缺陷如果没有经过I函数接受数据则会导致SQL注入

这个缺陷因为执行流程篇幅过长,分析会在后面的文章进行详细分析。

_do_mobile_login这里也存在,两个一样的类型。

0x3.2 后台广告编辑一处注入

application\Admin\Controller\AdController.class.php

Line 37-42

1
2
3
4
5
6
7
function edit(){
$id=I("get.id");
$ad=$this->ad_model->where("ad_id=$id")->find();
$this->assign($ad);
$this->display();
}

where出直接拼接了参数,I函数默认使用的是htmlspecialchars

并不会转义单引号所以这里存在了注入

0x3.3 后台友情连接处注入

application\Admin\Controller\LinkController.class.php

1
2
3
4
5
6
7
8
//第一处注入
function edit(){
$id=I("get.id");//下方使用了拼接字符
$link=$this->link_model->where("link_id=$id")->find();
$this->assign($link);
$this->assign("targets",$this->targets);
$this->display();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function toggle(){
if(isset($_POST['ids']) && $_GET["display"]){
$ids = implode(",", $_POST['ids']);
$data['link_status']=1;
if ($this->link_model->where("link_id in ($ids)")->save($data)!==false) {
$this->success("显示成功!");
} else {
$this->error("显示失败!");
}
}
if(isset($_POST['ids']) && $_GET["hide"]){
$ids = implode(",", $_POST['ids']);
$data['link_status']=0;
if ($this->link_model->where("link_id in ($ids)")->save($data)!==false) {
$this->success("隐藏成功!");
} else {
$this->error("隐藏失败!");
}
}
}

这几处注入都是因为自己拼接了字符串 或者使用原生的POST、GET获得数据没有任何过滤引起的注入

这种注入在此程序中有多处就不再一一列举

0x3.4 Getshell

没有getshell的审计,怎么能满足各位呢:)

application\Admin\Controller\RouteController.class.php

1
2
3
4
5
6
7
function index(){

$routes=$this->route_model->order("listorder asc")->select();
sp_get_routes(true);
$this->assign("routes",$routes);
$this->display();
}

首先获取了数据库中route表内容的数据,跟进sp_get_routes

先从数据库中取出status为1的路由规则,然后对取出来的路由数组进行htmlspecialchars_decode解码

在Line 1225-1227

1
2
3
4
5
6
7
8
9
F("routes",$cache_routes);
$route_dir=SITE_PATH."/data/conf/";
if(!file_exists($route_dir)){
mkdir($route_dir);
}

$route_file=$route_dir."route.php";

file_put_contents($route_file, "<?php\treturn " . stripslashes(var_export($all_routes, true)) . ";");

这里判断了/data/conf/文件夹是否存在不存在会创建,接着往下看可以看到使用file_put_contents函数进行了文件操作。将从数据表获取的数据以键值的形式存进route.php。这里要注意的是stripslashes

,如果没有这个函数的存在,数据中的'会被var_export全部转义。

因为多了一个stripslashes去除转义符,那么getshell的思路就有了:

  1. 首先在后台添加路由到数据库内,不必担心添加数据时是否会被过滤.

  2. 添加后转到路由规则设置的首页

  3. 首页执行index()将数据库里的内容写入route.php

保存后访问route.php看看是否成功

成功getshell

0x4 篇外

代码审计需要有足够的耐心和细心,此处的getshell处,笔者也是找了很久才发现。

多多阅读各位前辈公开的代码审计案例,以及乌云1000php,可以学习到一些不错的思路。

笔者已经搭建了乌云1000php,在这里贴上地址:http://php.evi1s.com/

当然此程序还有其他的漏洞,例如变量覆盖,xss等这里笔者就留给各位学习代码审计的道友去自行审计学习了

如果内容有错误处,请联系笔者,避免带偏其他人。