模糊查询中必须禁用${}拼接,应使用#{}绑定参数并由数据库或mybatis解析阶段处理%符号,同时在controller层校验空值、空白符及非法字符。

模糊查询里用 LIKE 加 % 是 SQL 注入高发区,只要用了 ${} 或手动字符串拼接 "%" + name + "%" 且没做输入净化,就等于给攻击者开了数据库的后门。
别用 ${} 做模糊匹配
${} 是纯文本替换,不走预编译,任何单引号、括号、注释符都会原样进 SQL。比如传参 admin%' OR '1'='1,最终生成的语句就是 WHERE name LIKE '%admin%' OR '1'='1%',条件恒真。
- 唯一能用
${}的场景是动态表名或列名——但模糊查询不在此列 - 哪怕加了
<trim></trim>或replace也拦不住嵌套注入,比如admin%' OR 1=1 -- - MyBatis-Plus 的
queryWrapper.like("name", name)看似方便,但它只防语法注入,不防语义误判(如a%b_c里的%和_会被当通配符)
安全写法:全走 #{} + 数据库函数 或 <bind></bind>
所有用户输入必须绑定为参数,% 符号由数据库生成或 MyBatis 解析阶段拼接,不能在 Java 层裸拼再传入。
- 推荐写法:
WHERE name LIKE CONCAT('%', #{name}, '%')——#{name}是预编译参数,CONCAT在 MySQL 侧执行,无注入风险 - 兼容性更强的写法:
<bind name="pattern" value="'%' + name + '%'"></bind> WHERE name LIKE #{pattern}—— 拼接发生在 MyBatis XML 解析时,不是 SQL 字符串拼接 - MyBatis-Plus 若需严格匹配通配符,得显式加
escape:queryWrapper.like("name", "a%b_c").escape('\'),并在 SQL 中声明ESCAPE ''
空值和边界输入必须提前拦截
很多接口崩掉不是因为注入,而是空字符串或空白符触发了数据库报错(比如旧版 MySQL 对 %% 敏感),反而暴露堆栈细节,帮攻击者探路。
- Controller 层必须校验:
if (name == null || name.trim().isEmpty()) { throw new IllegalArgumentException("name 不能为空"); } - 更稳妥做法是统一转成
Optional.ofNullable(name).filter(s -> !s.trim().isEmpty()).orElse(null),避免后续逻辑收到 null 或空白 - 如果前端传的是 raw JSON 或 URL 编码后的字符串,还要注意解码后是否含非法控制字符(如

