高 基础
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 ${} 是字符串拼接,有注入风险!
// 仅在动态表名/列名等无法参数化的场景使用,且必须白名单校验java2. 输入校验(补充手段)
// 白名单校验(强推荐)
if (!ALLOWED_SORT_FIELDS.contains(sortField)) {
throw new IllegalArgumentException("Invalid sort field");
}
// 类型校验
Long userId = Long.parseLong(input); // 数字类型天然防注入java3. 最小权限原则
-- 应用连接数据库的账号不应有 DROP/CREATE 权限
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_user'@'%';
-- 不给 DROP TABLE / ALTER TABLE / FILE 等危险权限sql4. 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 就不会注入”——${} 仍然是拼接,不当使用照样注入