面试知识库
基础

SQL注入原理与防御#

一句话答案#

SQL 注入是攻击者通过拼接恶意 SQL 片段篡改查询逻辑,根本防御是使用参数化查询(PreparedStatement / MyBatis #{} ),让用户输入永远作为参数值而非 SQL 结构的一部分。

核心要点

注入原理#

// 危险:字符串拼接 SQL
String sql = "SELECT * FROM users WHERE name = '" + input + "'";

// 攻击者输入: ' OR '1'='1
// 最终 SQL: SELECT * FROM users WHERE name = '' OR '1'='1'
// → 返回所有用户数据!

// 更危险的输入: '; DROP TABLE users; --
// 最终 SQL: SELECT * FROM users WHERE name = ''; DROP TABLE users; --'
// → 删除整张表!
java

常见攻击类型#

类型手法危害
Union 注入' UNION SELECT password FROM admin--窃取其他表数据
布尔盲注' AND 1=1-- / ' AND 1=2--逐位猜解数据
时间盲注' AND SLEEP(5)--通过响应时间判断条件真假
堆叠注入'; DROP TABLE x;--执行任意 SQL
二次注入先存入恶意数据,后续查询时触发绕过输入过滤

防御方案(按优先级)#

1. 参数化查询(根本解决)

// JDBC PreparedStatement
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE name = ?");
ps.setString(1, input);  // input 永远被当作值,不会被解析为 SQL

// MyBatis #{} (底层就是 PreparedStatement)
<select id="findUser">
  SELECT * FROM users WHERE name = #{name}
</select>

// MyBatis ${} 是字符串拼接,有注入风险!
// 仅在动态表名/列名等无法参数化的场景使用,且必须白名单校验
java

2. 输入校验(补充手段)

// 白名单校验(强推荐)
if (!ALLOWED_SORT_FIELDS.contains(sortField)) {
    throw new IllegalArgumentException("Invalid sort field");
}

// 类型校验
Long userId = Long.parseLong(input); // 数字类型天然防注入
java

3. 最小权限原则

-- 应用连接数据库的账号不应有 DROP/CREATE 权限
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_user'@'%';
-- 不给 DROP TABLE / ALTER TABLE / FILE 等危险权限
sql

4. WAF + ORM 框架

  • Web 应用防火墙拦截常见注入模式
  • ORM 框架(JPA/MyBatis)默认参数化

MyBatis 中的安全实践#

<!-- 安全:参数化 -->
WHERE id = #{id} AND status = #{status}

<!-- 危险:字符串拼接(仅在白名单校验后的动态列名使用) -->
ORDER BY ${sortColumn}

<!-- 安全的动态 SQL -->
<if test="status != null">AND status = #{status}</if>
<foreach collection="ids" item="id" open="(" close=")" separator=",">
  #{id}
</foreach>
xml
面试回答(2分钟版)

SQL 注入的本质是用户输入被当作 SQL 语句的一部分执行。比如登录时用户名输入 ' OR '1'='1,如果后端用字符串拼接 SQL,就会变成 WHERE name=” OR ‘1’=‘1’ 绕过认证。根本防御是参数化查询:JDBC 用 PreparedStatement,MyBatis 用 #{},它们把用户输入和 SQL 结构分离——输入永远作为参数值传递,数据库不会将其解析为 SQL 语法。MyBatis 的 ${} 是字符串拼接,有注入风险,只在动态表名/列名场景使用且必须白名单校验。补充措施包括:输入白名单校验、数据库账号最小权限(不给 DROP 权限)、WAF 拦截。二次注入是先存恶意数据再在后续查询中触发,防御同样是所有 SQL 都参数化,不只是入口处。

追问与易错

追问方向:

  • “PreparedStatement 为什么能防注入?”→ SQL 结构和参数分开发送给 DB,参数不会被 parse 为语法
  • “MyBatis #{} 和 ${} 区别?”→ #{} 是 PreparedStatement 参数化,${} 是字符串拼接
  • “ORM 就完全安全了吗?”→ 不是,原生 SQL 查询、动态拼接场景仍可能注入
  • “什么场景必须用 ${}?”→ 动态表名/列名/ORDER BY 字段名(SQL 参数化不支持标识符)

易错点:

  • ❌ “前端校验就够了”——前端校验可被绕过,必须后端防御
  • ❌ “转义特殊字符能防注入”——不完全可靠(编码绕过),参数化才是根本方案
  • ❌ “用了 MyBatis 就不会注入”——${} 仍然是拼接,不当使用照样注入