SQL注入漏洞代码审计 详解
1.什么是SQL注入?
应用程序直接将用户输入(如表单、URL参数、Cookie等)拼接到SQL查询中,未做严格过滤或参数化处理
2.常见的注入类型
| 类型 | 特点 | 示例 |
|---|---|---|
| 联合查询 | 通过UNION合并恶意查询结果 |
UNION SELECT 1,2,3-- |
| 布尔盲注 | 通过页面响应差异判断条件真假 | AND 1=1 vs AND 1=2 |
| 时间盲注 | 利用数据库延时函数(如SLEEP())判断 |
AND IF(1=1, SLEEP(5), 0)-- |
| 报错注入 | 触发数据库错误回显敏感信息 | AND 1=CONVERT(int, (SELECT @@version))-- |
3.代码审计
3.1 jdbc中的sql注入
原因:
其主要原因是后端代码将前端获取的参数动态直接拼接到SQL语句中使用 java.sql.Statement 执行SQL语句从而导致SQL注入漏洞的出现。
在这里关键点有两个:①、动态拼接参数。②、使用 java.sql.Statement 执行SQL语句。
3.1.1 使用 Statement(不安全,易 SQL 注入)
import java.sql.*;
public class UnsafeExample {
public static void main(String[] args) throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "pass");
Statement stmt = conn.createStatement();
String userInput = "admin"; // 用户输入
String sql = "SELECT * FROM users WHERE username = '" + userInput + "'";
ResultSet rs = stmt.executeQuery(sql); // ⚠️ 直接拼接 SQL,存在注入风险
while (rs.next()) {
System.out.println(rs.getString("username"));
}
}
}
3.1.2 安全写法
使用 PreparedStatement(安全,防止 SQL 注入)
import java.sql.*;
public class SafeExample {
public static void main(String[] args) throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "pass");
String sql = "SELECT * FROM users WHERE username = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "admin"); // 自动转义特殊字符,防止 SQL 注入
ResultSet rs = pstmt.executeQuery(); // ✅ 安全查询
while (rs.next()) {
System.out.println(rs.getString("username"));
}
}
}
3.1.2 总结
| 方法 | 适用场景 | 是否安全 |
|---|---|---|
Statement.executeQuery() |
静态 SQL(无用户输入) | ❌ 不安全(易注入) |
PreparedStatement.executeQuery() |
动态 SQL(含用户输入) | ✅ 安全(参数化查询) |
3.2 order by注入
在SQL语句中, order by 语句用于对结果集进行排序。 order by 语句后面需要是字段名或者字段位置。在使用 PreparedStatement 预编译时,会将传递任意参数使用单引号包裹进而变为了字符串。
如果使用预编译方式执行 order by 语句,设置的字段名会被数据库认为是字符串,而不在是字段名。因此,在使用 order by 时,就不能使用 PreparedStatement 预编译了。
connection conn = DriverManager.getConnection(url, user, password);
String sql = "select * from users" + " order by " + id;//动态拼接
PreparedStatement preparestatement = conn.prepareStatement(sql);
ResultSet rs = preparestatement.executeQuery();
while (rs.next()) {
String reUsername = rs.getString("username");
String resPassword = rs.getString("password");
String info = String.format("%s: %s\n", reUsername, resPassword);
result.append(info);
}
3.4 Mybatis sql注入
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎
所有的 JDBC 代码以及设置参数和获取结果集的工作。
3.4.1 Mybatis中#{}和${}区别
在Mybatis中拼接SQL语句有两种方式:一种是占位符 #{} ,另一种是拼接符 ${} 。
占位符 #{} :对传入的参数进行预编译转义处理。类似 JDBC 中的 PreparedStatement 。
比如: select * from user where id = #{number} ,
如果传入数值为1,最终会被解析成
select * from user where id = "1"。
拼接符 ${} :对传入的参数不做处理,直接拼接,进而会造成SQL注入漏洞。
比如: select * from user where id = ${number} ,
如果传入数值为1,最终会被解析成
select * from user where id = 1 。
{} 可以有效防止SQL注入漏洞。 ${} 则无法防止SQL注入漏洞。
<!-- ✅ 安全写法 -->
<select id="getUserByName" resultType="User">
SELECT * FROM users WHERE name = #{name}
</select>
<!-- ❌ 危险写法 -->
<select id="getUserByName" resultType="User">
SELECT * FROM users WHERE name = '${name}'
</select>
| 场景 | 正确做法 | 错误做法 |
|---|---|---|
| WHERE 条件 | #{} |
${} |
| ORDER BY 字段名 | 白名单校验 + ${} |
直接 ${} |
| LIKE 查询 | CONCAT('%', #{keyword}, '%') |
'%${keyword}%' |
| MyBatis-Plus | wrapper.eq() |
wrapper.apply("sql=" + input) |
3.5 in 注入
IN语句 :常用于where表达式中,其作用是查询某个范围内的数据。
比如:
select * from where field in (value1,value2,value3,…);
如上图,in 在查询某个范围数据是会用到多个参数,在 Mybtis中如果直接使用占位符 #{} 进行查询会将这些参数看做一个整体,查询会报错。在开发过程中 开发会直接 ${} 从而造成sql注入
正确的使用办法 是使用foreach +in配合的方法
<!-- where in 查询场景 -->
<select id="select" parameterType="java.util.List" resultMap="BaseResultMap">
SELECT *FROM user WHERE name IN
<foreach collection="names" item="name" open="(" close=")" separator=",">
#{name}
</foreach>
</select>
| 写法方式 | 安全性 | 是否支持多个值 | 是否推荐 |
|---|---|---|---|
#{} |
✅ 安全 | ❌ 不支持多个值 | ❌ 不适用 |
${} |
❌ 危险 | ✅ 支持多个值 | ❌ 不推荐 |
<foreach> + #{} |
✅ 安全 | ✅ 支持多个值 | ✅ 推荐 |
3.5 like注入
LIKE语句 :在一个字符型字段列中检索包含对应子串的。
比如:
select * from users where username like admin
使用like语句进行查询时如果使用占位符 #{} 查询时程序会报错(大家可自行调试)。
比如:
select * from users where username like '%#{username}%'
因此经验不足的开发人员可能会使用拼接符 ${} 对参数进行查询,从而造成了SQL注入漏洞。
比如:
select * from users where username like '%${username}%'
正确的写法:配合CONCAT关键字 使用
SELECT * FROM users WHERE name like CONCAT("%", #{name}, "%")
浙公网安备 33010602011771号