Java安全编码之SQL注入
字数 1334 2025-08-15 21:31:37
Java安全编码之SQL注入防护指南
1. 框架介绍
当前Java项目中广泛采用的两个持久层框架:
- Hibernate:全自动ORM框架,自动生成SQL语句
- MyBatis:支持自定义SQL的持久层框架,因其非侵入性逐渐取代Hibernate
2. Hibernate中的SQL注入防护
2.1 环境配置
- SpringBoot 2.3.1.RELEASE
- MySQL 5.7.20
2.2 常见漏洞场景
2.2.1 SQL注入(字符串拼接方式)
// 危险示例:直接拼接SQL
String sql = "from User u where u.userName like '%" + username + "%'";
攻击方式:
- 直接使用SQLMap等工具可轻松注入
2.2.2 HQL注入
特点:
- 利用难度大于SQL注入
- 系统表通常未映射,难以获取系统信息
- 复杂语句支持较差
2.2.3 预编译防护(推荐方式)
// 安全示例:使用setParameter预编译
Query query = this.entityManager.createQuery(
"from User u where u.userName like :userName", User.class);
query.setParameter("userName", "%"+username+"%");
预编译机制:
- 单引号会被转义为两个单引号(' → '')
- 反斜杠会被转义为双反斜杠(\ → \)
- 根据参数类型调用不同方法(setString/setInt等)
底层实现:
- 通过
ClientPreparedQueryBindings.setString方法处理参数 - 对特殊字符进行转义处理
3. MyBatis中的SQL注入防护
3.1 安全使用方式(#{}预编译)
<!-- 安全示例:使用#{}预编译 -->
<select id="getList" resultType="User">
SELECT * FROM user_tbl WHERE username LIKE #{param}
</select>
特点:
- 预编译机制与Hibernate相同
- 参数会被正确转义处理
3.2 危险使用方式(${}字符串拼接)
<!-- 危险示例:使用${}字符串拼接 -->
<select id="getList" resultType="User">
SELECT * FROM user_tbl WHERE username LIKE '${param}'
</select>
风险:
- 直接拼接SQL语句,易受SQL注入攻击
- SQLMap等工具可轻松注入
3.3 Order By特殊处理
常见误区:
- "Order By不会参与预编译"是错误的
- 实际是预编译会使Order By参数被引号包裹而失效
解决方案:
- 将Order By字段直接写死在代码中
- 使用白名单校验Order By参数
- 通过返回数据顺序差异获取信息(盲注)
3.4 useServerPrepStmts参数
重要说明:
- 只有JDBC开启
useServerPrepStmts=true才是真正的服务端预编译 - 但字符串拼接方式即使开启预编译也无效
- JDBC默认不开启此参数的原因在于预编译机制本身已提供足够防护
4. 综合防护方案
4.1 优先使用预编译
- 在Hibernate中使用setParameter
- 在MyBatis中使用#{}
4.2 无法预编译时的替代方案
-
类型严格化
- 数字参数规范为Integer/Long等类型
- 在数据库操作前会进行类型检查
-
过滤方案
- 使用Spring AOP添加前置Filter
- 全局过滤有害字符(可能影响正常业务)
- 使用注解方式过滤特定参数(推荐)
-
工具类
- 使用Apache Jakarta Commons提供的过滤方法
4.3 其他建议
- 避免直接拼接SQL语句
- 对用户输入进行严格校验
- 使用最小权限原则配置数据库账户
- 定期进行安全审计和渗透测试
5. 关键总结
- 预编译是防护SQL注入的核心机制,能使用预编译的地方必须使用
- 理解框架底层处理机制(如Hibernate的HQL、MyBatis的#{}和${}区别)
- 特殊场景(如Order By)需要特殊处理
- 无法预编译时要有替代方案(类型检查、过滤等)
- 防御要分层,不能仅依赖单一防护措施
通过遵循这些原则和实践,可以显著降低Java应用中的SQL注入风险。