题目
MyBatis动态SQL中如何防范SQL注入攻击?
信息
- 类型:问答
- 难度:⭐⭐
考点
动态SQL编写,SQL注入防范,MyBatis安全实践
快速回答
在MyBatis中防范SQL注入的核心要点:
- 优先使用
#{}占位符而非${}拼接SQL - 必须使用
${}时,严格过滤输入参数 - 对动态表名/列名使用
@Param注解明确指定安全值 - 复杂场景使用
<bind>或OGNL函数预处理参数 - 结合数据库权限控制最小化风险
一、原理说明
SQL注入是通过在输入参数中插入恶意SQL片段来破坏原始SQL逻辑的攻击方式。MyBatis提供两种参数处理方式:
- #{}(安全):预编译处理,将参数作为
PreparedStatement的参数占位符(?)传递 - ${}(危险):直接字符串替换,将参数值拼接到SQL语句中
二、代码示例
危险写法(存在注入风险)
<!-- 用户输入 ' OR 1=1 -- 将导致全表查询 -->
<select id="findByUserName" resultType="User">
SELECT * FROM users WHERE name = '${userName}'
</select>安全写法(预编译防注入)
<select id="findByUserName" resultType="User">
SELECT * FROM users WHERE name = #{userName}
</select>动态表名场景的安全处理
// Mapper接口定义
List<User> findByTable(@Param("safeTableName") String tableName,
@Param("userName") String userName);<select id="findByTable" resultType="User">
SELECT * FROM ${safeTableName} <!-- 需确保表名来自安全值 -->
WHERE name = #{userName} <!-- 预编译保证安全 -->
</select>三、最佳实践
- 默认使用#{}原则:所有业务参数必须使用
#{} - ${}使用规范:
- 仅用于动态表名、列名等非业务参数
- 配合
@Param注解明确参数用途 - 在Service层进行白名单校验(如只允许特定表名)
- 复杂过滤处理:
<select id="search" resultType="User"> <bind name="safeName" value="@com.example.SecurityUtil@sanitize(name)"/> SELECT * FROM users WHERE name LIKE #{safeName} </select> - 全局防御:
- 数据库账号按需分配最小权限
- 启用MyBatis的SQL日志审计
四、常见错误
- 错误1:在ORDER BY子句中误用
${sortField}
修复方案:// Java层校验排序字段合法性 if(!Arrays.asList("id","name").contains(sortField)) { sortField = "id"; } - 错误2:在IN查询中拼接字符串
WHERE id IN (${ids})
修复方案:<where> id IN <foreach item="id" collection="ids" open="(" separator="," close=")"> #{id} <!-- 每个元素单独预编译 --> </foreach> </where>
五、扩展知识
- MyBatis底层机制:
#{}最终调用PreparedStatement.setXxx()方法 - OGNL注入风险:动态SQL中的OGNL表达式可能被恶意利用(如
ORDER BY ${sort} ${order}) - 防御深度:即使使用
#{},仍需在Service层进行业务逻辑校验 - 工具推荐:使用SQLMap等工具定期进行注入测试