为什么 MyBatis 的 `${}` 会导致 SQL 注入

Miles
Miles
发布时间:2026-08-25 | 114人浏览过
原创

前言

不少人知道 MyBatis 里应优先用 #{},却很难准确解释 ${} 为什么会把 SQL 注入风险直接放大。本文从执行链路、典型误用和项目治理三个层面拆开说明,帮助你判断哪些动态写法只是图省事,哪些已经把用户输入变成了可执行 SQL。

不少团队知道 MyBatis 推荐优先使用 #{},却未必能准确说清 ${} 为什么会把风险直接带进生产环境。真正需要搞明白的,不只是“它会不会注入”,而是它在 SQL 生成链路里究竟做了什么、哪些看起来正常的动态写法最容易失守,以及上线前该靠什么办法把这类问题找出来。

SQL 构建阶段就已被拼接,预编译保护失效

${} 本质上是纯字符串替换。MyBatis 在解析动态 SQL 时,会直接把变量值原样插入 SQL 文本,再把最终结果交给 Statement 执行,而不是交给 JDBC 的 PreparedStatement 去做参数绑定。

展示 `${}` 在 SQL 构建阶段直接拼接用户输入、绕过预编译的暖色专栏配图
`${}` 为什么会直接绕过预编译把 `${}` 放进 SQL 后,变量会在构建阶段直接变成语句的一部分,预编译隔离随之失效。

这一步的区别非常关键:一旦使用 ${},用户输入就不再是“参数值”,而是直接变成 SQL 语法的一部分。数据库收到的不是带占位符的语句,而是一条已经拼好的完整 SQL,因此预编译隔离、参数转义和类型处理都不会生效。

例如传入:

username = "admin' OR '1'='1"

如果 SQL 写成:

WHERE username = '${username}'

最终执行结果就是:

SELECT * FROM user WHERE username = 'admin' OR '1'='1'

这里的条件恒为真,认证、查询限制甚至部分数据隔离逻辑都可能被直接绕过。问题不在于语句写得复杂,而在于输入内容已经进入 SQL 语法层,数据库会把它当成真实条件执行。

不转义、不加类型约束,注入面会继续扩大

很多人把风险理解成“单引号没处理好”,但 ${} 的问题远不止这一层。它替换后的内容不会自动加引号,不做空值处理,也没有类型约束,等于把一段完全不受控的文本直接塞进 SQL。

这意味着攻击者能构造的不只是恒真条件,还可能包括分号、注释符、子查询或耗时表达式。例如:

  • ORDER BY ${column} + 输入 id; DROP TABLE log --:在某些数据库驱动允许多语句执行时,可能触发破坏性操作。
  • ORDER BY ${column} + 输入 id, (SELECT SLEEP(5)):即使拿不到数据,也可能借助耗时函数拖慢接口响应,形成拒绝服务效果。
  • 只靠前端 JavaScript 过滤同样不可靠:只要后端没有做校验,十六进制编码、双写关键字等绕过方式依然能把恶意内容送进 SQL。

也就是说,${} 不只是“比 #{} 稍微危险一点”,而是直接放弃了参数边界。只要变量来源不可信,攻击面就不再局限于某一个字段,而会扩散到整条 SQL 的结构本身。

这些动态写法最容易埋雷

业务里最麻烦的地方在于,很多 ${} 看起来像是在“做动态 SQL”,甚至配合了 <if> 判断,乍看还挺合理。但只要实际拼接点仍然是 ${},安全性就没有建立起来。

展示 ORDER BY、WHERE 片段和 LIKE 模糊查询三类高危 `${}` 用法的暖色专栏配图
最常见的 `${}` 高危落点高危点往往不是复杂语句,而是看起来像“正常动态需求”的几个常见拼接位。

几个常见高危场景尤其值得优先排查:

  • WHERE ${condition}:把整段条件交给外部输入,本质上等于给了一个 SQL 片段入口,和开放 SQL 编辑器没有本质差别。
  • AND ${sqlFragment}:问题比直接拼整句更隐蔽,审查代码时很容易误判成“只是补充条件”。
  • LIKE '%${keyword}%':这是模糊查询里最常见的坑。很多人以为外层有引号和百分号就安全了,实际上只要 ${keyword} 可控,注入照样成立,通配符还会放大查询范围。

这类写法往往分散在 XML 的多个分支里,人工肉眼很难一次看全。上线前如果不做一次全量检索,例如检查项目中所有 ${ 的使用位置,就很容易把问题代码带进生产。

真正要防的是失控变量,而不是语法本身

在真实项目里,${} 经常出现在 ORDER BY、表名、字段名这类无法直接用 #{} 绑定的位置。这里的难点不在于“能不能动态”,而在于动态内容是否可控。

展示 `${}` 在排序字段、表名、字段名场景下必须绑定白名单或枚举映射的暖色专栏配图
`${}` 的可用前提:变量必须被约束能否使用 `${}` 不是唯一问题,更关键的是变量是否被白名单、枚举或后端映射牢牢限制。

如果变量来自 HTTP 请求、配置中心、MQ 消息,或者任何外部输入源,而代码里又没有白名单、枚举映射或严格校验,那么风险就已经具备落地条件。此时即便业务本意只是“支持排序字段切换”或“按表分片查询”,也仍然可能被利用。

更稳妥的思路是把所有 ${} 都视为高风险点,只允许它绑定到明确受控的值,例如:

  • 排序字段只能从固定枚举中选择,而不是直接接收前端传入的列名;
  • 表名、字段名只能来自后端维护的映射关系,不能透传用户输入;
  • 所有使用 ${} 的地方都要能追溯参数来源,并有清晰的校验逻辑。

换句话说,真正难防的不是大家不知道该优先用 #{},而是不知道项目里还有哪些地方悄悄写了 ${},以及这些变量有没有被任何人直接改到。只要这两件事说不清,风险就不算真正收住。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表