spring boot 2 的 sql 注入漏洞主因是 mybatis ${} 误用或 jdbc 拼接,修复核心是堵住拼接入口、约束动态 sql、守住输入边界;必须禁用 ${} 替代 #{}, 例外场景需白名单校验,like 和 in 应用标准安全写法,配合 dto 校验与全局异常处理。

Spring Boot 2 中的 SQL 注入漏洞,绝大多数源于 MyBatis(尤其是 ${} 的误用)或手写 JDBC 拼接,而非框架本身缺陷。修复核心不是“加个依赖”,而是堵住参数拼接入口、约束动态 SQL 场景、守住输入边界。
别用 ${} 替代 #{},除非你清楚后果
MyBatis 的 ${} 是字符串替换,不走 PreparedStatement,等同于把用户输入原样塞进 SQL 字符串里。哪怕只在 ORDER BY 或 IN 子句中用一次,都可能被利用。
- 错误写法:
SELECT * FROM user WHERE status = ${status}→ 输入1 OR 1=1直接绕过条件 - 正确写法:
SELECT * FROM user WHERE status = #{status}→ MyBatis 自动绑定为预编译参数 - 例外场景(如
ORDER BY):必须白名单校验字段名,再拼接。例如 Java 层定义Map<string string> sortFields = Map.of("name", "user_name", "time", "create_time")</string>,取值前校验 key 是否存在,再用${sortFields.get(sortKey)}
LIKE 和 IN 查询不能靠改 $ 来“修”
看到 #{title} 在 LIKE '%#{title}%' 报错就换成 ${title}?这是典型“以毒攻毒”。MyBatis 提供了标准解法,不需要牺牲安全性。
-
LIKE:用concat('%', #{title}, '%')(MySQL)或'%' || #{title} || '%'(PostgreSQL),保持#{}语义 -
IN:必须用<foreach></foreach>标签,例如id IN <foreach item="id" collection="ids" open="(" separator="," close=")">#{id}</foreach>。传入List<long></long>,MyBatis 会为每个元素生成独立占位符 - 切忌:
IN (${ids})+String.join(",", ids)拼接 —— 这等于手动实现${},且无类型检查
Controller 层不做校验,等于没防
参数化查询能防注入,但拦不住超长输入、非法字符爆破、或故意触发数据库报错泄露结构。Spring Boot 的校验机制要真正启用,而不是只加个 @Valid 注解就完事。
- DTO 必须标注
@Size(max = 50)、@Pattern(regexp = "^[a-zA-Z0-9_]+$")等约束,否则@Valid不生效 - 全局异常处理器要捕获
ConstraintViolationException并返回友好提示,避免堆栈或 SQL 片段泄露到响应体 - 对明确不允许出现的字符(如单引号、分号、
--、/*),可在 Filter 或 AOP 中做前置清洗,但注意:清洗不能替代校验,只能作为补充
XML 文件里搜 $ 是最有效的代码审计动作
所有 MyBatis XML 映射文件,用 IDE 的 Ctrl+Shift+F 搜索 $(注意转义,搜 \$ 更准),逐行确认每个 ${} 是否属于白名单控制的动态字段/表名/排序,还是直接来自用户请求参数。
- 发现
${username}、${id}、${keyword}这类命名,基本可判定为高危点 - generator 自动生成的 XML 也要查 ——
Example类的orderByClause字段默认用${},需手动覆盖或重写 - 不要依赖“测试没报错”来判断安全;攻击者构造的输入往往不在常规测试路径里
真正难的不是写对一个 #{},而是在模糊查询、批量 ID、动态排序、多租户表名这些真实业务场景里,坚持不走捷径。每次想用 ${} 前,先问自己:这个值是否完全可控?有没有更安全的替代路径?











