YCCMS v3.4 代码审计教学文档
文档概述
本教学文档旨在通过剖析YCCMS(一个内容管理系统)v3.4版本的源代码,系统地讲解代码审计的流程、常见漏洞的挖掘方法、漏洞原理及修复方案。YCCMS是一个典型的MVC(Model-View-Controller)架构的PHP应用程序,非常适合用于学习PHP代码审计。
审计目标: YCCMS v3.4
核心发现: 存在未授权访问、任意文件删除、文件上传绕过、代码执行等高危漏洞。
一、 基础信息收集与分析
在开始深入审计前,需要对目标应用有一个基本的了解。
-
URL结构分析:
- 通过观察后台管理功能的URL,可以发现其采用
a和m参数分别指代控制器(Controller)和方法(Method)。 - 示例:
http://127.0.0.1/admin/?a=article&m=adda=article表示调用ArticleAction控制器。m=add表示调用该控制器下的add()方法。
- 对应的文件路径为:
/controller/ArticleAction.class.php。
- 通过观察后台管理功能的URL,可以发现其采用
-
框架识别:
- 尝试搜索
version、thinkphp等关键字,未发现使用已知主流框架(如ThinkPHP)的痕迹。结论:这是一个原生PHP编写的、自研的MVC框架应用。
- 尝试搜索
二、 漏洞审计详解
漏洞1:未授权访问导致的管理员密码篡改
-
漏洞类型: 访问控制缺失
-
危险等级: 高危
-
漏洞位置:
admin/?a=admin&m=update -
涉及文件:
/controller/AdminAction.class.php中的update()方法。/model/AdminModel.class.php中的editAdmin()方法。
-
漏洞原理:
- 在
AdminAction.class.php的update()方法中,直接调用了$this->_model->editAdmin()来修改管理员信息。 - 整个调用链没有任何的会话验证或权限检查(例如,没有检查用户是否已登录、是否是管理员)。
- 因此,攻击者无需登录,即可直接通过访问该URL并传入新用户名和密码的参数,篡改任意管理员账号的密码。
- 在
-
审计技巧:
- 入口扫描: 重点关注所有
/admin/?a=...的入口点。 - 权限校验缺失: 检查每个控制器方法的首部是否存在统一的权限验证代码(如
$this->checkLogin();)。在本案例中,整个应用缺乏这种全局或基类的控制。
- 入口扫描: 重点关注所有
-
举一反三:
通过此漏洞可以发现,整个后台的权限校验体系是缺失的。审计员应系统性地检查所有后台功能(如文章删除a=article&m=delete),很可能都存在类似的未授权访问问题。
漏洞2:未授权任意文件删除
-
漏洞类型: 权限绕过、路径遍历
-
危险等级: 高危
-
漏洞位置:
admin/?a=pic&m=delall -
涉及文件:
/controller/PicAction.class.php中的delall()方法。 -
漏洞原理:
- 该方法用于批量删除图片。它接收一个名为
pid[]的数组参数,数组中包含要删除的图片文件名。 - 代码直接将文件名与基础路径
ROOT_PATH.'/uploads/'进行拼接,形成完整的文件路径$_filePath。 - 随后直接调用
unlink($_filePath)进行删除,未对文件名进行任何过滤。 - 由于该功能同样缺乏权限校验,攻击者可以构造特殊的文件名进行路径遍历,删除服务器上的任意文件。
- 该方法用于批量删除图片。它接收一个名为
-
攻击复现(PoC):
- URL:
http://127.0.0.1/admin/?a=pic&m=delall - POST数据:
pid[0]=../../../config/database.config.php chkall=on send=删除选中图片 - 此Payload可以删除网站根目录下
config文件夹中的数据库配置文件,导致网站瘫痪。
- URL:
漏洞3/4:文件上传漏洞(两处)
这两处漏洞原理相似,都是由于对上传文件的MIME类型验证存在缺陷。
-
漏洞类型: 文件上传绕过
-
危险等级: 高危
-
漏洞位置1:
admin/?a=call&m=upfile&type=content(最终调用upLoad方法) -
漏洞位置2:
admin/?a=call&m=xhUp&type=content -
涉及文件:
/controller/CallAction.class.php中的upLoad()和xhUp()方法。 -
漏洞原理:
- 代码在验证文件类型时,依赖于
$_FILES['pic']['type']的值。 $_FILES['pic']['type']是由浏览器端提交的,可以被攻击者随意篡改。- 验证逻辑
in_array($this->type, $this->typeArr)只是简单判断这个值是否在允许的类型数组中(如image/png)。 - 攻击者只需上传一个恶意文件(如
.php后缀),然后在Burp Suite等工具中抓包,将Content-Type头部修改为image/png,即可绕过检查,成功上传Webshell。
- 代码在验证文件类型时,依赖于
-
修复方案对比:
- 错误方式: 信任客户端提交的MIME类型。
- 正确方式: 使用服务器端验证,例如检查文件的真实类型(使用
finfo_file(FILEINFO_MIME_TYPE)函数)和文件扩展名白名单机制。
漏洞5:代码执行漏洞
-
漏洞类型: 代码注入
-
危险等级: 严重
-
漏洞位置: 多个入口文件,如
/admin/index.php,/search/index.php,/config/count.php。 -
涉及文件:
/public/class/Factory.class.php。 -
漏洞原理:
Factory.class.php是程序的调度核心。它通过$_a = self::getA();获取URL中的a参数。- 然后使用
file_exists()检查对应的控制器文件是否存在。 - 最后,使用
eval()函数动态实例化控制器类:eval('self::$_obj = new '.ucfirst($_a).'Action();'); - 这里的
$_a参数未经任何过滤就直接拼接进了eval语句,造成了代码注入漏洞。
-
漏洞利用详解:
- 目标: 闭合
new语句,并注入恶意代码。 - Payload构造:
a=Factory();phpinfo();//../ - 代码解释:
ucfirst($_a)将a的值变为Factory();phpinfo();//../。- 拼接后的eval语句为:
self::$_obj = new Factory();phpinfo();//../Action(); //在PHP中是注释符,其后的../Action();会被注释掉。- 最终执行的代码是:
self::$_obj = new Factory(); // 实例化Factory类(如果该类存在且可实例化) phpinfo(); // 执行phpinfo()函数
- 利用结果: 访问
http://127.0.0.1/admin/index.php?a=Factory();system('calc');//../甚至可以在服务器上弹出计算器(如果服务器是Windows系统且权限允许),证明漏洞危害极大。
- 目标: 闭合
-
关键技巧:
- 利用
file_exists()在Windows下的路径解析特性(//被当作\,..用于目录遍历),使检查能够通过,指向一个实际存在的文件(如../Action.class.php),从而绕过file_exists()的检查。
- 利用
三、 漏洞修复方案总结
针对以上发现的漏洞,提出以下系统性的修复建议:
-
强化权限校验体系:
- 建立一个基类控制器(如
BaseAction.class.php),在其构造函数中调用统一的权限验证方法(如checkPermission())。 - 让所有后台的控制器都继承自这个基类,确保每个后台操作在执行前都经过了权限验证。
- 建立一个基类控制器(如
-
安全的文件删除:
- 不要直接使用用户输入拼接路径。建议通过加密的ID映射到具体的文件路径,或对文件名进行严格的白名单过滤(只允许字母、数字、短横线、下划线等)。
- 在删除前,再次验证目标文件是否位于允许操作的目录内(如
/uploads/)。
-
加固文件上传功能:
- 采用白名单机制: 只允许指定的、安全的文件扩展名(如
.jpg,.png,.gif)。 - 服务器端验证: 使用
finfo_file()函数检测文件的真实MIME类型,绝不信任$_FILES['file']['type']。 - 重命名文件: 将上传的文件重命名为随机字符串(如UUID),并保留原有扩展名,防止文件名注入和恶意文件被执行。
- 隔离存储: 将上传的文件存储在Web根目录之外,通过专门的脚本来访问,避免直接执行。
- 采用白名单机制: 只允许指定的、安全的文件扩展名(如
-
杜绝代码注入:
- 绝对禁止使用
eval(): 尤其是在动态实例化类的地方。应使用安全的反射机制或预先定义好的映射数组来替代。 - 示例(安全方式):
$allowedControllers = ['article', 'admin', 'pic', ...]; $controllerName = $_GET['a']; if (!in_array($controllerName, $allowedControllers)) { die('Invalid controller'); } $className = ucfirst($controllerName) . 'Action'; self::$_obj = new $className(); // 安全的方式,无需eval
- 绝对禁止使用
四、 代码审计方法论总结
通过本次YCCMS的审计,可以总结出以下方法论:
- 信息收集: 理解应用的URL路由、目录结构和技术栈。
- 入口点定位: 梳理所有用户可控的输入点(GET/POST参数、Cookie、HTTP头)。
- 敏感函数追踪: 全局搜索
unlink(文件删除)、move_uploaded_file(文件上传)、eval/assert(代码执行)、system/exec(命令执行)、SQL查询函数等,逆向追踪用户输入是否能够不受控制地到达这些函数。 - 权限校验审查: 系统性检查关键功能是否存在权限校验,是否存在越权访问(水平越权、垂直越权)。
- 逻辑漏洞挖掘: 关注业务流程,如验证码绕过、密码修改、订单支付等环节是否存在逻辑缺陷。
希望这份详尽的教学文档能帮助你深入理解代码审计的思维和技巧。