项目地址:
https://gitee.com/oufu/ofcms/tree/V1.1.3/
参考文章:
https://www.yijinglab.com/specialized/20220526170417
https://mp.weixin.qq.com/s/dZAqUVpKr6fA_Lq7nJTmbw
https://mp.weixin.qq.com/s/1agUDocOUbFgbiO83Qz5-w
本篇文章是个人在代码审计学习阶段对于jfinal下的一个CMS的审计,漏洞的审计还是比较经典的过程,存在的问题希望大家可以提出,共同交流学习
代码审计思路
pom.xml的依赖 dependency
ofcms-V1.1.3\ofcms-admin\src\main\webapp\WEB-INF\web.xml 过滤器 filter
全局搜索关键字 配合 网页 功能点灰盒 审计
1. SQL注入
1.1. jfinal框架下操作数据库简介
首先分析他使用的数据库框架是Mybatis还是JDBC
随便找了一条语句,发现是使用Db这个类去执行的SQL语句,去Db看一下
发现是jfinal框架下的ORM
ORM(Object-Relational Mapping,对象关系映射)是一种技术,用于在面向对象编程语言(如 Java、C# 等)和关系数据库之间建立关联。通过 ORM,开发者可以使用面向对象的编程方式来操作数据库,而无需编写大量的 SQL 语句。
该框架下的数据库配置在JFWebConfig.java中
跟入ADMIN_CONFIG,可以看到数据库配置文件的存放位置
这里简介一些关于Db的操作,这样方便搜索关键字进行审计
Db.save: 插入一条记录。该方法需要提供表名和一个Record
对象。
Record user = new Record().set("name", "Alice").set("age", 25);
Db.save("user", user);
Db.find:查询所有记录
List<Record> users = Db.find("SELECT * FROM user");
for (Record user : users) {
System.out.println(user.get("name"));
Db.findFirst:查询单条记录
Record user = Db.findFirst("SELECT * FROM user WHERE id = ?", 1);
if (user != null) {
System.out.println(user.get("name"));
}
Db.query:查询单个字段
String name = Db.queryStr("SELECT name FROM user WHERE id = ?", 1);
System.out.println(name);
Db.update: 更新记录。该方法可以直接执行更新 SQL 语句,也可以传入Record
对象。
直接执行更新 SQL 语句
int updatedRows = Db.update("UPDATE user SET age = ? WHERE id = ?", 30, 1);
System.out.println("Updated rows: " + updatedRows);
使用Record
对象
Record user = Db.findById("user", 1).set("age", 30);
Db.update("user", user);
下文用到了Db.update,这里来看一下源码
可以看到使用了prepareStatement,但是要注意的是,这里虽然使用了prepareStatement但是没有使用??占位符,所以直接将sql语句输入依然可以执行
Db.delete:
直接执行删除 SQL 语句
int deletedRows = Db.delete("DELETE FROM user WHERE id = ?", 1);
System.out.println("Deleted rows: " + deletedRows);
根据主键删除记录
boolean success = Db.deleteById("user", 1);
System.out.println("Deleted successfully: " + success);
Db.tx:
可以执行事务操作: 事务是一组操作,这些操作要么全部成功,要么全部失败,从而保证数据的一致性和完整性。
该方法接受一个实现了IAtom
接口的匿名类或 lambda 表达式,所有的数据库操作将在一个事务中执行
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.IAtom;
public class DbExample {
public static void main(String[] args) {
boolean success = Db.tx(new IAtom() {
@Override
public boolean run() throws SQLException {
Db.update("UPDATE user SET age = ? WHERE id = ?", 30, 1);
Db.update("UPDATE user SET age = ? WHERE id = ?", 35, 2);
return true; // 返回 true 表示提交事务,返回 false 表示回滚事务
}
});
System.out.println("Transaction success: " + success);
}
}
Db.batch:
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;
import java.util.ArrayList;
import java.util.List;
public class DbExample {
public static void main(String[] args) {
List<Record> users = new ArrayList<>();
users.add(new Record().set("name", "Bob").set("age", 20));
users.add(new Record().set("name", "Charlie").set("age", 22));
int[] results = Db.batchSave("user", users, 100);
for (int result : results) {
System.out.println("Batch result: " + result);
}
}
}
Db.getSqlPara: 用于获取带有参数的 SQL 语句,结合了 SQL 模板和参数的功能,将动态生成的 SQL 语句与参数安全地绑定在一起,从而避免了 SQL 注入风险。
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.SqlPara;
import java.util.HashMap;
import java.util.Map;
public class Example {
public static void main(String[] args) {
// 假设 params 包含查询参数
Map<String, Object> params = new HashMap<>();
params.put("sqlid", "findUserById");
params.put("id", 1);
// 获取 SqlPara 对象
SqlPara sqlPara = Db.getSqlPara(params.get("sqlid").toString(), params);
// 使用 SqlPara 执行查询
List<Record> users = Db.find(sqlPara);
for (Record user : users) {
System.out.println(user.get("name"));
}
}
}
1.2. 关键字查找注入点
根据上面学习到的内容,我们基本上可以确定思路了,先查找执能够行SQL语句的关键字,然后看执行语句时传入的参数(非Db.getSqlPara、或是预编译传参)笔者这里写了一个表达式
Db.(find|findFirst|update|delete|save|batch)\s*(
用来搜索jfinal框架下对于SQL语句操作的方法
都看了一遍,大部分是用了Db.getSqlPara或者是预编译,但是有一处直接使用getPara进行传参
跟过去,找到控制器路径
抓包控制器路径,传入恶意参数,判断确实存在SQL注入漏洞
payload:
update of_cms_link set link_name=updatexml(1,concat(0x7e,(user())),0) where link_id = 4
2. XSS
2.1. jfinal框架下接收参数关键字
挖掘XSS漏洞时,要寻找"输入点"与"输出点",由于"输入点"和"输出点"可能不在同一个业务流中,在挖掘这类漏洞时,可以考虑以下方法提高效率:
黑白盒结合。(定位可能存在漏洞的地点)。
通过功能,接口名,表名,字段名等角度去搜索(关键字)。
这里的话重点是代码审计,就不去黑盒的测试了,介绍一下白盒下的思路
首先寻找接收参数的关键字:getParameter,但是别忘了当