${} 本身不是漏洞但误用会导致sql注入,因其直接字符串拼接且无任何处理;#{} 并非万能,因preparedstatement无法用于表名、字段名等sql结构位置。

直接说结论:${} 本身不是漏洞,但用错地方就是高危风险;#{} 也不是万能解药,强行替换会导致 SQL 报错、程序崩溃。
为什么 ${} 会引发 SQL 注入
因为 ${} 是字符串拼接,MyBatis 拿到参数后不做任何处理,原样塞进 SQL 字符串里,再交给数据库执行。数据库根本分不清哪是代码、哪是数据——只要拼进去的内容带 OR '1'='1、UNION SELECT 或注释符 --,它就照单全收。
- 典型错误:WHERE name = '${name}',用户传
admin' --,SQL 变成 WHERE name = 'admin' --',后面条件全被注释掉 - 更隐蔽的:ORDER BY ${sortField},传
id, (SELECT password FROM user LIMIT 1),直接拖库 -
${}不加引号、不转义、不类型检查,等同于手写String sql = "SELECT * FROM user WHERE id = " + userId;
为什么不能简单把所有 ${} 换成 #{}
#{} 底层走 JDBC PreparedStatement,所有参数都被当成「值」绑定,自动加引号或类型转换。但 SQL 中有些位置根本不能放“值”——比如表名、字段名、排序方向、LIMIT 的第一个数字。
- FROM ${tableName} → 改成 FROM #{tableName},实际执行变成 FROM 'user_202605',MySQL 直接报
Unknown table 'user_202605' - ORDER BY ${field} ${order} → #{field} 会变成 ORDER BY 'created_time',排序失效(按字符串字面值排)
- LIMIT ${(page-1)*size}, #{size} → #{(page-1)*size} 语法错误,
#{}不支持表达式计算
哪些地方必须用 ${},又怎么守住安全底线
必须用 ${} 的场景极少,集中在 SQL 语法结构层:表名、字段名、GROUP BY 表达式、ORDER BY 方向、LIMIT 偏移量。这些地方不能参数化,但可以控制输入来源。
- 表名:Java 层硬编码白名单,如
Set.of("user", "order_v1", "order_v2"),校验tableName是否在其中 - 排序字段:前端只传简写 key(如
ctime),后端用Map.of("ctime", "created_at")映射,不接受原始字段名 - 排序方向:强制限定为
"asc"或"desc",其他值一律拒绝或 fallback 到"asc" - 绝对禁止:HTTP 请求参数、JSON body 字段、前端下拉框 value 值,未经白名单校验就进
${}
模糊查询 like 怎么写才安全
这是高频误用区:很多人写 WHERE name LIKE '%${name}%',以为加了百分号就没事——其实照样注入。正确做法是把通配符拼在 Java 层,再用 #{}:
WHERE name LIKE #{searchPattern}
对应 Java 代码:param.setSearchPattern("%" + userInput + "%");,让 MyBatis 绑定时自动处理引号和转义。
真正难的不是写对语法,而是每次看到 ${} 都要停下来问一句:这个值是不是来自不可信输入?它在 SQL 里扮演的是「数据」还是「结构」?漏掉这一问,白名单和 #{} 都救不了你。











