审计JAVA下的SQL注入
字数 2305 2025-08-24 23:51:18
Java应用SQL注入审计与防御指南
1. JDBC下的SQL注入
1.1 Statement与SQL注入
Statement是JDBC中执行SQL语句的基本方式,但存在严重的安全隐患:
String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
注入示例:
- 输入:
username = admin' and 1=1#,password = any - 最终SQL:
SELECT * FROM users WHERE username = 'admin' and 1=1#' AND password = 'any' - 由于
#是MySQL注释符,实际执行:SELECT * FROM users WHERE username = 'admin' and 1=1
1.2 PreparedStatement防御机制
PreparedStatement通过预编译和参数绑定防止SQL注入:
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();
防御原理:
- 预编译:SQL语句结构在预编译阶段确定
- 参数化查询:用户输入作为数据而非SQL语句部分
- 自动转义:特殊字符如单引号会被转义(
'→\')
1.3 预编译的误用与风险
1.3.1 不安全拼接
// 错误示例:先拼接再预编译
String sql = "SELECT * FROM users WHERE username = '" + req.getParameter("username")
+ "' AND password = '" + req.getParameter("password") + "'";
PreparedStatement pstmt = connection.prepareStatement(sql);
1.3.2 IN语句处理
不安全方式:
String sql = "delete from users where id in("+delIds+")";
安全解决方案:
public int gradeDelete(Connection con, String delIds) throws Exception {
String[] ids = delIds.split(",");
String placeholders = String.join(",", Collections.nCopies(ids.length, "?"));
String sql = "delete from users where id in(" + placeholders + ")";
PreparedStatement pstmt = con.prepareStatement(sql);
for(int i = 0; i < ids.length; i++) {
pstmt.setInt(i+1, Integer.parseInt(ids[i]));
}
return pstmt.executeUpdate();
}
1.3.3 LIKE语句处理
不安全方式:
String sql = "select * from users where username like '%" + con + "%'";
安全解决方案:
String sql = "select * from users where username like ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, "%" + con + "%");
1.3.4 ORDER BY限制
ORDER BY子句无法使用预编译占位符,因为字段名不能加引号:
// 无法使用预编译的安全方式
String sql = "select * from users order by ?";
// 必须使用拼接,需严格过滤
String column = "username"; // 应做白名单校验
String sql = "select * from users order by " + column;
1.4 PreparedStatement实现原理
- 词法分析:解析SQL语句结构
- 参数标记:
?作为占位符 - 输入处理:
- 对字符串类型参数自动添加引号
- 对特殊字符进行转义
- 数值类型直接嵌入
关键代码路径:
PreparedStatement.setString()→StringUtils.escapeString()- 对单引号等特殊字符添加转义符
- 最终生成的安全SQL语句结构固定
2. MyBatis下的SQL注入
2.1 #{}与${}的区别
| 特性 | #{} | ${} |
|---|---|---|
| 处理方式 | 预编译参数化 | 直接字符串替换 |
| 安全性 | 安全 | 不安全 |
| 引号处理 | 自动添加 | 不处理 |
| 适用场景 | 值参数 | 表名、列名等非值部分 |
2.2 ${}导致的注入
Mapper示例:
<select id="getUser" resultType="User">
SELECT * FROM users WHERE username = '${username}'
</select>
注入攻击:
- 输入:
username = admin' or '1'='1 - 生成SQL:
SELECT * FROM users WHERE username = 'admin' or '1'='1'
2.3 安全使用模式
2.3.1 LIKE语句
<!-- MySQL -->
<select id="search" resultType="User">
SELECT * FROM users WHERE username LIKE CONCAT('%', #{keyword}, '%')
</select>
<!-- Oracle -->
<select id="search" resultType="User">
SELECT * FROM users WHERE username LIKE '%' || #{keyword} || '%'
</select>
2.3.2 IN语句
<select id="getByIds" resultType="User">
SELECT * FROM users WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
2.3.3 ORDER BY处理
不安全方式:
ORDER BY ${columnName}
安全方案:
- 使用Java代码校验排序字段
// 白名单校验 List<String> validColumns = Arrays.asList("id", "username", "create_time"); if(!validColumns.contains(columnName)) { columnName = "id"; // 默认值 } - 在Mapper中使用条件判断
<select id="getUsers" resultType="User"> SELECT * FROM users ORDER BY <choose> <when test="orderBy == 'name'">username</when> <when test="orderBy == 'time'">create_time</when> <otherwise>id</otherwise> </choose> </select>
2.4 MyBatis实现原理
2.4.1 #{}处理流程
- XML解析:
XMLScriptBuilder识别#{}模式 - 参数标记:将
#{param}替换为? - 参数绑定:通过
ParameterHandler设置参数值 - 最终执行:调用JDBC的
PreparedStatement
2.4.2 ${}处理流程
- 文本替换:直接替换
${param}为参数值 - SQL拼接:生成完整SQL语句
- Statement执行:使用普通
Statement执行
3. Hibernate下的SQL注入
3.1 HQL注入
不安全HQL:
String hql = "from User where username = '" + username + "' and password = '" + password + "'";
Query<User> query = session.createQuery(hql, User.class);
安全参数绑定方式:
-
位置参数:
String hql = "from User where username = ?1 and password = ?2"; Query<User> query = session.createQuery(hql, User.class); query.setParameter(1, username); query.setParameter(2, password); -
命名参数:
String hql = "from User where username = :name and password = :pass"; Query<User> query = session.createQuery(hql, User.class); query.setParameter("name", username); query.setParameter("pass", password);
3.2 原生SQL注入
不安全方式:
String sql = "select * from users where username = '" + username + "'";
Query<User> query = session.createNativeQuery(sql, User.class);
安全方式:
String sql = "select * from users where username = ?";
Query<User> query = session.createNativeQuery(sql, User.class);
query.setParameter(1, username);
3.3 Hibernate实现原理
Hibernate最终仍调用JDBC的PreparedStatement实现参数化查询:
- 对HQL进行解析和转换
- 生成带占位符的SQL
- 通过
ParameterBinder绑定参数 - 调用
PreparedStatement.setXXX()方法
4. 防御最佳实践
- 优先使用预编译:始终使用
PreparedStatement或#{} - 严格校验输入:对不可预编译部分(如ORDER BY)进行白名单校验
- 最小权限原则:数据库用户仅授予必要权限
- 防御层叠:结合输入验证、输出编码等多层防御
- 安全审计:定期进行代码审计和渗透测试
防御矩阵:
| 场景 | 安全方案 | 风险等级 |
|---|---|---|
| 普通查询 | 预编译参数化 | 低 |
| LIKE查询 | CONCAT/函数拼接 | 中 |
| IN查询 | 动态生成占位符 | 中 |
| ORDER BY | 白名单校验 | 高 |
| 表名/列名 | 应用层校验或映射 | 高 |
通过理解这些框架的SQL注入原理和防御机制,开发者可以构建更安全的Java应用程序,有效防范SQL注入攻击。