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}, "%")
posted on 2025-07-21 17:34  在努力学习的杰瑞  阅读(85)  评论(0)    收藏  举报