${} 会触发 SQL 注入,因其不做转义直接文本替换,使用 Statement 执行,使用户输入可注入恶意 SQL;修复需按场景区分:用户数据改用 #{},动态结构须白名单校验或 foreach 处理。

直接说结论:${} 不是“写法问题”,而是字符串拼接行为本身 —— 它绕过了预编译,把用户输入原样塞进 SQL 文本里,数据库照单全收执行。
为什么 ${} 会触发 SQL 注入
MyBatis 解析 ${} 时不做任何转义或类型处理,只做纯文本替换。比如传入 id = "1 OR 1=1",WHERE id = ${id} 就变成 WHERE id = 1 OR 1=1,整条 WHERE 条件失效。
- 底层调用的是
Statement(非PreparedStatement),SQL 在拼完才交给数据库 - 攻击者可注入单引号、括号、分号、子查询甚至
SLEEP(),只要语法合法就能执行 - 哪怕外层用了
#{},只要子查询里有${},整个链路就失守
哪些地方最常误用 ${} 导致风险
不是所有 ${} 都危险,但凡参数来自用户输入(HTTP 请求、表单、URL 参数、前端 JS 拼接),就极可能被利用。
-
ORDER BY ${sortField}:传入id, (SELECT password FROM users LIMIT 1)可拖库 -
WHERE status IN (${statusList}):传入"'shipped', 'cancelled', (SELECT @@version)"触发联合查询 -
FROM ${tableName}:若表名未白名单校验,传入user; DROP TABLE user--可删表 -
LIKE '%${keyword}%':传入admin%' OR '1'='1绕过权限校验
修复方案必须分场景落地
不能一刀切“禁用 ${}”,得看它在干什么。关键判断标准:这个值是否属于 SQL 结构(如字段名、表名、关键字),还是用户数据?
- 如果是用户数据(ID、姓名、状态码等)→ 全部改用
#{},哪怕要模糊匹配,也用CONCAT('%', #{keyword}, '%')或LIKE #{keyword}+ Java 层加通配符 - 如果是动态结构(排序字段、表名、IN 列表)→ 必须加约束:
- 排序字段:用白名单校验,
if (!Arrays.asList("id", "name", "create_time").contains(sortField)) throw new IllegalArgumentException(); - IN 列表:不用
${statusList},改用<foreach></foreach>:WHERE status IN <foreach item="s" collection="statusList" open="(" separator="," close=")">#{s}</foreach> - 表名:只允许从配置或枚举中取值,禁止任何外部输入直接透传;分表场景下,表名应由日期/租户 ID 等不可篡改字段生成
- 排序字段:用白名单校验,
最容易被忽略的测试盲区
开发自测时常用固定值(如 sortField="name"、keyword="test"),根本看不出问题。上线后一旦遇到带单引号的用户名、含逗号的状态列表、或前端传来的原始 SQL 片段,${} 就立刻暴露为执行入口。
真正有效的验证方式是:对每个含 ${} 的 mapper 方法,手动构造含 '、;、(SELECT、UNION 的参数发请求,观察是否报错或返回异常数据 —— 报错不等于安全,返回多余数据才是真漏洞。

















