Mybatis-Flex浅析
字数 1312 2025-08-06 08:35:00
Mybatis-Flex 安全机制与SQL注入防护分析
0x00 关于Mybatis-Flex
MyBatis-Flex是一个MyBatis增强框架,具有轻量、高性能与灵活性的特点。它可以轻松连接任何数据库,其内置的QueryWrapper能显著减少SQL编写工作并降低出错概率。
依赖引入方式:
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot-starter</artifactId>
<version>1.5.6</version>
</dependency>
0x01 已有的安全措施
1.1 OrderBy排序安全防护
风险背景:
OrderBy子句内容通常是动态生成的,预编译无法处理动态SQL语句,这类场景常存在SQL注入风险。
防护实现:
@RequestMapping("/getAllUser")
public ApiResponse<List<User>> getAllUser(String sort) {
QueryWrapper queryWrapper = QueryWrapper.create().select().orderBy(sort);
List<User> userList = userMapper.selectListByQuery(queryWrapper);
return new ApiResponse<>(200, "Success", userList);
}
安全机制:
- 通过
StringQueryOrderBy构建排序字段 - 在构造方法中调用
SqlUtil.keepOrderBySqlSafely进行安全检查 - 白名单控制:只允许字母、数字、下划线、空格、逗号和点号
- 非法字符(如
/)会抛出IllegalArgumentException
对比SpringData:
SpringData限制更严格,只允许数字、字母、'.'、'_'和''字符:
private static final Predicate<String> predicate =
Pattern.compile("^[0-9a-zA-Z_\\.\$\$]*$").asPredicate();
1.2 列名column检查
风险背景:
GROUP BY子句的动态性使得预编译阶段难以确定具体分组方式,存在SQL注入风险。
防护实现:
@RequestMapping("/groupByCareer")
public ApiResponse<List<Map>> groupByCareer() {
QueryWrapper queryWrapper = QueryWrapper.create()
.select("career, COUNT(*)")
.groupBy("CAREER");
List<Map> result = userMapper.selectRowsByQuery(queryWrapper);
return new ApiResponse<>(200, "Success", result);
}
安全机制:
- 通过
QueryColumn构建列名字段 - 调用
SqlUtil.keepColumnSafely进行安全检查:- 检查空白字符(
Character.isWhitespace) - 检查不安全字符(
isUnSafeChar方法)
- 检查空白字符(
QueryMethods函数:
类似COUNT等方法也有相同安全检查机制。
0x02 常见SQL注入场景
2.1 Mybatis原生注解&XML配置
风险点:
- 使用原生MyBatis功能时,
${}参数仍存在注入风险 - 需检查所有使用
${}标注的参数
2.2 QueryWrapper注入风险
2.2.1 动态列名注入
安全方式:
QueryWrapper queryWrapper = QueryWrapper.create().select(new QueryColumn("columnName"));
- 通过
QueryColumn构建时会自动安全检查
风险方式:
@RequestMapping("/getColumn")
public ApiResponse<List<Map>> getTbale(String column) {
QueryWrapper queryWrapper = QueryWrapper.create().select(column);
List<Map> result = userMapper.selectRowsByQuery(queryWrapper);
return new ApiResponse<>(200, "Success", result);
}
- 直接传入String通过
StringQueryColumn构建时无安全检查 - 可执行
1/0等恶意SQL
2.2.2 Where动态条件注入
风险示例:
@RequestMapping("/getUserByFirstName")
public ApiResponse<List<User>> getUserByFirstName(String firstName) {
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.where("FIRST_NAME='"+firstName+"'");
List<User> result = userMapper.selectRowsByQuery(queryWrapper);
return new ApiResponse<>(200, "Success", result);
}
- 直接SQL拼接导致注入
防护方案:
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.where("FIRST_NAME=?", firstName);
- 使用预编译占位符
其他风险点:
and (...)和or (...)等where条件拓展也存在类似风险
2.3 Db + Row 工具类风险
风险示例:
@RequestMapping("/getUserByDbRow")
public ApiResponse<List<Row>> getUserByDbRow(String name) {
String listsql = "select * from user where first_name = '"+name+"'";
List<Row> result = Db.selectListBySql(listsql);
return new ApiResponse<>(200, "Success", result);
}
- 直接SQL拼接导致注入
防护方案:
String listsql = "select * from user where first_name = ?";
List<Row> result = Db.selectListBySql(listsql, name);
- 使用预编译占位符
- 对于无法预编译的场景(如排序/动态表名列名)应进行限制处理
安全开发建议
- 优先使用预编译:所有动态参数都应使用
?占位符 - 避免直接拼接SQL:特别是where条件、表名、列名等
- 使用安全构建方式:
- 排序使用
orderBy(QueryOrderBy...) - 列名使用
QueryColumn
- 排序使用
- 严格限制动态内容:对于必须动态的内容实施白名单控制
- 定期安全审计:检查所有SQL构建点,特别是
${}和直接拼接处
通过遵循这些安全实践,可以充分利用Mybatis-Flex的便利性同时避免SQL注入风险。