mybatis中like查询必须用#{}而非${},因${}是字符串替换、不预编译,易导致sql注入;正确做法是java层拼接通配符后传入#{},或用数据库函数如concat('%', #{name}, '%'),同时对排序字段等动态内容实施白名单校验。

MyBatis 中 like 查询必须用 #{},不是 ${}
直接拼接 ${username} 到 SQL 里(比如 WHERE name LIKE '%${username}%')等于给攻击者递刀——任何含 '、--、OR 1=1 的输入都会触发注入。根本原因在于 ${} 是字符串替换,不走预编译;而 #{} 会交由 JDBC 预处理,参数被当作纯数据绑定,数据库根本不会把它当 SQL 解析。
常见错误场景包括:前端传参带单引号、括号或注释符;后端未校验就透传到 XML 或注解 SQL 中;用 CONCAT('%', ${name}, '%') 这类写法依然危险——只要出现 ${} 就不可信。
- 正确做法是把通配符拼在 Java 层:
queryWrapper.like("name", "%" + username + "%") - 或在 XML 中用函数封装:
WHERE name LIKE CONCAT('%', #{name}, '%')(MySQL) - Oracle/PostgreSQL 用
WHERE name LIKE '%' || #{name} || '%' - SQL Server 用
WHERE name LIKE '%' + #{name} + '%'
QueryWrapper 的 like 方法默认安全,但要注意空值和 null 处理
QueryWrapper 的 like("column", value) 内部已做参数化,本身不会注入。但若 value 是用户原始输入且含恶意内容(如 "admin' --"),它仍会被当作普通字符串匹配,不会执行 SQL —— 这正是预编译的保护机制。真正容易出问题的是开发者自己绕过它,比如手动拼 String sql = "LIKE '%" + input + "%'"。
另一个坑是空值判断逻辑错位:如果写成 .like(username != null, "name", username),但没对 username 做 trim 或长度限制,攻击者可能传入超长 payload 拖慢查询,甚至触发数据库日志溢出。
- 始终用
StringUtils.isNotBlank(username)替代username != null - 对模糊查询字段加长度限制,比如
if (username.length() > 50) throw new IllegalArgumentException() - 避免在
like条件里混用or()和未校验字段,例如.like(...).or().eq("status", userStatus)中userStatus若来自前端且未白名单校验,仍可能被用于注入(虽概率低,但非零)
排序字段、表名、列名动态拼接时,${} 不可避免,必须白名单校验
像 ORDER BY ${sortField} 这种场景,#{} 无效,因为数据库不允许参数化列名。此时唯一安全的做法是 Java 层硬编码白名单,而不是靠正则或黑名单过滤。
常见错误是写 if (sortField.matches("[a-zA-Z_]+")) —— 这类宽松正则无法防住 id, (SELECT ...) 这种子查询注入;或者只校验一次、放在 service 层,但 controller 层已透传脏数据。
- 定义静态白名单:
private static final Set<string> ALLOWED_SORT_FIELDS = Set.of("id", "name", "created_time");</string> - 校验必须在进入 DAO 前完成,最好在 Controller 或 DTO 绑定阶段
- 禁止从任意 HTTP 参数、JSON 字段直接映射到排序字段,哪怕加了
@RequestParam String sort也得立刻校验 - MyBatis-Plus 的
orderByAsc("column")等方法内部不校验,仍需上层兜底
Ransack 的 q[name_cont] 类参数天然带净化,但要关掉 ignore_unknown_conditions
Rails 应用用 Ransack 做搜索时,q[name_cont] 这种参数默认走预编译,底层已转义,比手写 SQL 安全得多。但它的“容错模式”会默默忽略非法谓词(比如 q[name_eq_any]),让攻击者试探有效参数名而不报错。
更危险的是默认 default_predicate = 'cont'(包含匹配),而 cont 对应 SQL 的 LIKE '%...%' —— 如果没配索引,海量数据下会全表扫描,性能崩盘的同时也放大了注入探测成功率。
- 在
config/initializers/ransack.rb中设config.ignore_unknown_conditions = false,让非法参数直接 400 - 显式设
config.default_predicate = 'eq',避免模糊匹配滥用 - 对高频搜索字段建前缀索引(如
INDEX idx_name ON users(name(20))),否则LIKE '%xxx'必然失效
#{} 或 QueryWrapper 处理;列名、表名、排序字段必须由代码白名单控制;所有外部输入都要有长度、类型、格式三重守门——漏掉任何一环,模糊查询就可能变成后门。











