MyBatis SQL注入防护与动态表名安全处理指南
MyBatis安全防护:SQL注入防护与动态表名处理实战
一、SQL注入风险与防护基础
SQL注入是Web应用中最常见的安全漏洞之一,攻击者通过构造特殊输入改变SQL语义,可能导致数据泄露、篡改甚至服务器沦陷。MyBatis作为半自动ORM框架,其SQL编写方式直接影响应用安全性。
1.1 #{}
与${}
的本质区别
// 安全写法
@Select("SELECT * FROM users WHERE id = #{userId}")
User getUserById(@Param("userId") String userId);
// 危险写法(存在注入风险)
@Select("SELECT * FROM users WHERE id = ${userId}")
User getUserByIdUnsafe(@Param("userId") String userId);
核心差异:
#{}
:参数化查询(PreparedStatement)- 输入值会被正确处理为参数,不会改变SQL结构
- 自动进行类型转换和特殊字符转义
${}
:字符串替换(直接拼接)- 原样替换为字符串,可能改变SQL语义
- 无任何防护措施
1.2 安全编码实践建议
- 默认使用#{}:95%的场景都应使用参数化方式
${}的合法场景:
- 动态表名/列名(需额外防护)
- SQL关键字(ORDER BY等)
- 输入校验:即使使用#{}也应做业务层校验
二、动态表名/列名的安全处理
当业务需要动态指定表名或列名时(如多租户系统),必须谨慎处理以避免注入风险。
2.1 白名单校验方案
<!-- 安全示例:通过白名单控制可访问的表 -->
<select id="queryByTable" resultType="map">
SELECT * FROM
<choose>
<when test="tableName == 'users'">users</when>
<when test="tableName == 'products'">products</when>
<otherwise>default_table</otherwise>
</choose>
WHERE id = #{id}
</select>
2.2 程序代码校验方案
public List<Map<String, Object>> queryDynamicTable(String tableName, Long id) {
// 校验表名合法性
if (!isValidTableName(tableName)) {
throw new IllegalArgumentException("Invalid table name");
}
// 使用${}但已确保安全
return sqlSession.selectList(
"com.example.mapper.DynamicMapper.queryByTable",
Map.of("tableName", tableName, "id", id));
}
private boolean isValidTableName(String name) {
return name.matches("[a-zA-Z_][a-zA-Z0-9_]*");
}
2.3 高级防护方案(拦截器)
@Intercepts({
@Signature(type= StatementHandler.class,
method="prepare",
args={Connection.class, Integer.class})
})
public class TableCheckInterceptor implements Interceptor {
private static final Set<String> ALLOWED_TABLES = Set.of("users", "products");
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler handler = (StatementHandler) invocation.getTarget();
String sql = handler.getBoundSql().getSql();
// 检测SQL中的动态表名
if (sql.contains("${table}")) {
String tableName = /* 提取参数值 */;
if (!ALLOWED_TABLES.contains(tableName)) {
throw new SQLException("Illegal table access");
}
}
return invocation.proceed();
}
}
三、综合防护策略
3.1 防御体系分层
输入层:
- 正则表达式校验
- 长度/格式限制
持久层:
- 严格使用#{}
- 必须使用${}时配合白名单
数据库层:
- 应用账号最小权限
- 敏感表单独授权
3.2 MyBatis特殊场景处理
LIKE查询安全写法:
<select id="searchUsers" resultType="User">
SELECT * FROM users
WHERE name LIKE CONCAT('%', #{keyword}, '%')
</select>
IN查询安全写法:
<select id="getByIds" resultType="User">
SELECT * FROM users
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
四、审计与监控
SQL日志审计:
- 开启MyBatis的debug日志
- 监控非常规SQL模式
安全扫描:
- 使用SQLMap等工具定期扫描
- 代码静态分析(如SonarQube)
应急响应:
- 建立SQL注入的识别流程
- 准备数据回滚方案
最佳实践总结
禁用特性清单:
- 避免使用
${}
拼接WHERE条件 - 禁止前端直接传递SQL片段
- 避免使用
安全开发流程:
- 代码审查重点检查SQL写法
- DAO层单元测试包含注入测试用例
框架升级:
- 及时更新MyBatis版本
- 关注安全公告(CVE漏洞)
通过合理使用MyBatis的特性并建立多层防御,可以显著降低SQL注入风险,建议将上述防护措施纳入项目开发规范,并通过自动化工具确保执行。