通读审计之DOYOCMS
字数 2173 2025-08-15 21:30:29

DOYOCMS代码审计与漏洞分析教学文档

一、前言

本教学文档基于对DOYOCMS建站系统的完整代码审计过程,详细记录了审计思路、漏洞发现过程及利用方法。文档采用"通读审计"方式,将整个审计过程完整呈现,特别适合对MVC框架不熟悉的审计者学习。

二、MVC框架基础分析

1. 核心文件结构

  • index.php:入口文件

    • 包含config.php(定义常量和数据库信息)
    • 设置模板路径:$doyoConfig['view']['config']['template_dir'] = APP_PATH.'/template/'.$doyoConfig['ext']['view_themes'];
    • 包含sys.php(核心系统文件)
  • sys.php:核心系统文件

    • 包含functions.php(功能函数)
    • 初始化全局配置:$GLOBALS['G_DY'] = spConfigReady(require(DOYO_PATH."/inc.php"), $doyoConfig);
    • 定义MVC核心类:syControllersyModelsyView

2. 关键函数分析

spConfigReady函数

function spConfigReady($useconfig, $preconfig = array()) {
    if(is_array($useconfig)) {
        foreach($useconfig as $key => $val) {
            $preconfig[$key] = $val;
        }
    }
    return $preconfig;
}

作用:合并两个配置数组

import函数

function import($sfilename) {
    if(TRUE == @is_readable($sfilename)) {
        require($sfilename);
        $GLOBALS['G_DY']['import_file'][md5($sfilename)] = TRUE;
        return TRUE;
    }
    return FALSE;
}

作用:安全包含文件,确保文件可读后才包含

syClass函数

function syClass($class_name, $args = null, $sdir = null, $force_inst = FALSE) {
    // 类名合法性检查
    if(preg_match("/^[a-zA-Z0-9_]+$/", $class_name) == 0) syError("类定义不存在,请检查。");
    
    // 单例模式处理
    if(TRUE != $force_inst) if(isset($GLOBALS['G_DY']["inst_class"][$class_name])) 
        return $GLOBALS['G_DY']["inst_class"][$class_name];
    
    // 尝试加载类文件
    if(null != $sdir && !import($sdir) && !import($sdir.'/'.$class_name.'.php')) return FALSE;
    
    // 类是否存在检查
    $has_define = FALSE;
    if(class_exists($class_name, false) || interface_exists($class_name, false)) {
        $has_define = TRUE;
    } else {
        if(TRUE == import($class_name.'.php')) {
            $has_define = TRUE;
        }
    }
    
    // 实例化类
    if(FALSE != $has_define) {
        $argString = ''; $comma = '';
        if(null != $args) for($i = 0; $i < count($args); $i++) {
            $argString .= $comma . "\$args[$i]"; $comma = ', ';
        }
        eval("\$GLOBALS['G_DY']['inst_class'][\$class_name]= new \$class_name($argString);");
        return $GLOBALS['G_DY']["inst_class"][$class_name];
    }
    syError($class_name."类定义不存在,请检查。");
}

作用:动态加载和实例化类,实现MVC框架的核心功能

三、漏洞分析与利用

1. PHP任意文件替换漏洞

漏洞位置:自定义session处理机制中的文件写入操作

漏洞文件sysession.php

关键代码

function write($id, $data) {
    $filedir = $this->savePath;
    $filename = $filedir."/".$id.".php";
    $data = "<?php die();?>".$data;
    return file_put_contents($filename, $data);
}

漏洞分析

  1. 程序使用自定义session存储机制,将session数据写入文件
  2. 写入前会在数据前添加<?php die();?>防止代码执行
  3. 但未对$id(即session ID)进行过滤,导致路径可控
  4. 利用file_put_contents特性,可以替换任意文件内容

利用条件

  • 能够控制session ID
  • 知道目标文件路径

利用方法

  1. 设置session ID为../include/inc.php
  2. 程序会将session数据写入/include/inc.php,替换原文件
  3. 由于写入内容包含<?php die();?>,会导致网站无法正常运行

防御建议

  1. 对session ID进行严格过滤,禁止目录穿越字符
  2. 使用固定目录存储session文件
  3. 使用PHP原生session机制

2. SSRF漏洞

漏洞位置a_sys控制器的template_cache方法

漏洞文件source/admin/a_sys.php

关键代码

function template_cache() {
    $y = $this->syArgs('y', 2);
    $url = "http://".$y."/template/admin/index/index.html";
    $html = spUrlOpen($url);
    // ...
}

漏洞分析

  1. 使用syArgs('y', 2)获取参数,其中第二个参数2表示过滤级别
  2. 过滤级别2存在缺陷(后面详细分析)
  3. 直接将用户输入的y参数拼接到URL中
  4. 使用spUrlOpen函数发起请求,导致SSRF

过滤问题分析
syArgs方法中:

case 2: // 仅安全过滤
    $this->args = $this->filters($this->args, 'safe');
    break;

filters方法中的safe过滤:

function filters($value, $type) {
    // ...
    case 'safe':
        if(is_array($value)) {
            array_walk_recursive($value, array($this, 'arrays'));
        } else {
            $value = $this->arrays($value);
        }
        break;
    // ...
}

function arrays($value) {
    if(is_array($value)) {
        foreach($value as $k => $v) {
            $value[$k] = $this->arrays($v);
        }
    } else {
        $value = htmlspecialchars(addslashes($value));
    }
    return $value;
}

问题:array_walk_recursive不会修改原始数组,导致数组参数过滤失效

利用方法

admin.php?c=a_sys&a=template_cache&y=127.0.0.1:3306

可探测内网服务

防御建议

  1. 对URL参数进行严格校验,限制域名和端口
  2. 使用白名单方式限制可访问的地址
  3. 修复array_walk_recursive过滤问题

3. SQL注入漏洞

漏洞位置a_classtypes控制器的alledit方法

漏洞文件source/admin/a_classtypes.php

关键代码

function alledit() {
    $orders = $this->syArgs('orders', 2);
    // ...
    $this->db->update(array('types'=>$orders), $where);
}

漏洞分析

  1. 使用syArgs('orders', 2)获取参数,同样存在过滤级别2的问题
  2. 直接将用户输入的orders参数用于SQL更新操作
  3. 框架自带的update方法未充分过滤输入

利用方法

?c=a_classtypes&a=alledit&orders[]=1' or updatexml(2,concat(0x7e,(version())),0) or'

防御建议

  1. 使用预处理语句替代直接拼接SQL
  2. 加强输入过滤,特别是对数组参数的过滤
  3. 对数据库操作进行统一的参数绑定处理

四、审计技巧总结

  1. MVC框架审计要点

    • 跟踪控制器、模型、视图的调用关系
    • 关注路由解析过程($_REQUEST['c']$_REQUEST['a']
    • 分析核心函数如syClass的动态加载机制
  2. 漏洞挖掘方法

    • 全局搜索危险函数(file_put_contentseval等)
    • 分析自定义过滤函数的实现缺陷
    • 跟踪用户输入从获取到使用的完整流程
  3. 重点关注

    • 文件操作相关的路径控制
    • 数据库操作是否使用预处理
    • 网络请求相关的参数过滤

五、修复建议

  1. 整体安全加固

    • 实现严格的输入过滤机制
    • 使用PHP原生session替代自定义实现
    • 对文件操作进行路径检查和限制
  2. 代码层面改进

    • 修复array_walk_recursive过滤问题
    • 加强syArgs方法的过滤能力
    • 对数据库操作进行统一安全处理
  3. 架构层面建议

    • 实现前后端分离,减少服务端动态渲染
    • 使用成熟的框架替代自主实现MVC
    • 引入自动化安全测试流程

本教学文档详细记录了DOYOCMS系统的审计过程和发现的漏洞,可作为代码审计学习的典型案例。通过此案例,可以学习到MVC框架的审计方法、常见漏洞的挖掘技巧以及安全防御的最佳实践。

DOYOCMS代码审计与漏洞分析教学文档 一、前言 本教学文档基于对DOYOCMS建站系统的完整代码审计过程,详细记录了审计思路、漏洞发现过程及利用方法。文档采用"通读审计"方式,将整个审计过程完整呈现,特别适合对MVC框架不熟悉的审计者学习。 二、MVC框架基础分析 1. 核心文件结构 index.php :入口文件 包含 config.php (定义常量和数据库信息) 设置模板路径: $doyoConfig['view']['config']['template_dir'] = APP_PATH.'/template/'.$doyoConfig['ext']['view_themes']; 包含 sys.php (核心系统文件) sys.php :核心系统文件 包含 functions.php (功能函数) 初始化全局配置: $GLOBALS['G_DY'] = spConfigReady(require(DOYO_PATH."/inc.php"), $doyoConfig); 定义MVC核心类: syController 、 syModel 、 syView 2. 关键函数分析 spConfigReady函数 作用:合并两个配置数组 import函数 作用:安全包含文件,确保文件可读后才包含 syClass函数 作用:动态加载和实例化类,实现MVC框架的核心功能 三、漏洞分析与利用 1. PHP任意文件替换漏洞 漏洞位置 :自定义session处理机制中的文件写入操作 漏洞文件 : sysession.php 关键代码 : 漏洞分析 : 程序使用自定义session存储机制,将session数据写入文件 写入前会在数据前添加 <?php die();?> 防止代码执行 但未对 $id (即session ID)进行过滤,导致路径可控 利用 file_put_contents 特性,可以替换任意文件内容 利用条件 : 能够控制session ID 知道目标文件路径 利用方法 : 设置session ID为 ../include/inc.php 程序会将session数据写入 /include/inc.php ,替换原文件 由于写入内容包含 <?php die();?> ,会导致网站无法正常运行 防御建议 : 对session ID进行严格过滤,禁止目录穿越字符 使用固定目录存储session文件 使用PHP原生session机制 2. SSRF漏洞 漏洞位置 : a_sys 控制器的 template_cache 方法 漏洞文件 : source/admin/a_sys.php 关键代码 : 漏洞分析 : 使用 syArgs('y', 2) 获取参数,其中第二个参数2表示过滤级别 过滤级别2存在缺陷(后面详细分析) 直接将用户输入的 y 参数拼接到URL中 使用 spUrlOpen 函数发起请求,导致SSRF 过滤问题分析 : syArgs 方法中: filters 方法中的 safe 过滤: 问题: array_walk_recursive 不会修改原始数组,导致数组参数过滤失效 利用方法 : 可探测内网服务 防御建议 : 对URL参数进行严格校验,限制域名和端口 使用白名单方式限制可访问的地址 修复 array_walk_recursive 过滤问题 3. SQL注入漏洞 漏洞位置 : a_classtypes 控制器的 alledit 方法 漏洞文件 : source/admin/a_classtypes.php 关键代码 : 漏洞分析 : 使用 syArgs('orders', 2) 获取参数,同样存在过滤级别2的问题 直接将用户输入的 orders 参数用于SQL更新操作 框架自带的update方法未充分过滤输入 利用方法 : 防御建议 : 使用预处理语句替代直接拼接SQL 加强输入过滤,特别是对数组参数的过滤 对数据库操作进行统一的参数绑定处理 四、审计技巧总结 MVC框架审计要点 : 跟踪控制器、模型、视图的调用关系 关注路由解析过程( $_REQUEST['c'] 和 $_REQUEST['a'] ) 分析核心函数如 syClass 的动态加载机制 漏洞挖掘方法 : 全局搜索危险函数( file_put_contents 、 eval 等) 分析自定义过滤函数的实现缺陷 跟踪用户输入从获取到使用的完整流程 重点关注 : 文件操作相关的路径控制 数据库操作是否使用预处理 网络请求相关的参数过滤 五、修复建议 整体安全加固 : 实现严格的输入过滤机制 使用PHP原生session替代自定义实现 对文件操作进行路径检查和限制 代码层面改进 : 修复 array_walk_recursive 过滤问题 加强 syArgs 方法的过滤能力 对数据库操作进行统一安全处理 架构层面建议 : 实现前后端分离,减少服务端动态渲染 使用成熟的框架替代自主实现MVC 引入自动化安全测试流程 本教学文档详细记录了DOYOCMS系统的审计过程和发现的漏洞,可作为代码审计学习的典型案例。通过此案例,可以学习到MVC框架的审计方法、常见漏洞的挖掘技巧以及安全防御的最佳实践。