${} 是纯字符串替换,不走 JDBC 预编译,直接拼接 SQL 导致 SQL 注入风险;它不做转义、不加引号、无类型约束,可被用于注入恶意语句;常见高危场景包括动态 WHERE、ORDER BY、表名/字段名等,必须绑定白名单或严格校验。

`${}` 是纯字符串替换,不走 JDBC 预编译
MyBatis 解析 `${}` 时,会在 SQL 构建阶段(即动态 SQL 解析期)就把变量值原样插入到 SQL 字符串中,生成的最终语句直接交给 Statement 执行。它跳过了 JDBC 的 PreparedStatement 流程,数据库收到的就是“已拼好”的完整 SQL——用户输入的内容此时已是语法的一部分,而非待处理的参数值。
比如传入 username = "admin' OR '1'='1",配合 WHERE username = '${username}',最终执行的是:
SELECT * FROM user WHERE username = 'admin' OR '1'='1'
这个条件恒为真,攻击者就能绕过认证逻辑。
`${}` 不做任何转义或类型约束
${} 替换后的内容不会被加引号、不会做空值处理、不会校验类型,甚至连基本的单引号闭合都指望不上。攻击者可以自由注入分号、注释符、子查询甚至执行耗时操作:
-
ORDER BY ${column}+ 输入id; DROP TABLE log --→ 可能触发多语句执行(取决于数据库驱动配置) -
ORDER BY ${column}+ 输入id, (SELECT SLEEP(5))→ 拖慢响应,构成拒绝服务 - 哪怕前端做了 JS 过滤,后端没校验,照样被绕过(如用十六进制编码、双写关键字等)
哪些场景看似合理,实则高危
很多人误以为“用了 <if> 就安全”,但只要里面嵌了 ${},整个逻辑就失效。典型反例包括:
-
WHERE ${condition}:把整段 WHERE 子句交给前端拼,等于开放 SQL 编辑器 -
AND ${sqlFragment}:允许任意片段注入,比直接暴露更难审计 -
LIKE '%${keyword}%':模糊查询最常踩的坑,${keyword}一被注入,通配符就成帮凶
这些写法在代码里往往藏得深,上线前不 grep '\$\{' 全量扫描,根本发现不了。
真正难防的不是不会用 #{},而是不知道哪写了 <code>${}
很多项目里 ${} 出现在 order by、表名、字段名等位置,本意是支持动态结构,但没配白名单校验,也没做 Java 层预过滤。一旦参数来源不可控(比如来自 HTTP 请求、配置中心、MQ 消息),风险立刻落地。最稳妥的做法不是“能不能用”,而是“有没有人敢动这个变量”——所有 ${} 必须绑定明确的枚举、配置项或校验逻辑,且不能直连用户输入。

















