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

不是
为什么常被误认为“导致”注入
开发者看到
<if test="sortField != null">
ORDER BY ${sortField} ${sortOrder}
</if>
这里
- 攻击者传入
sortField=id; DROP TABLE users; --,生成SQL就是ORDER BY id; DROP TABLE users; -- ASC 的test表达式(如 sortField == 'name')本身是OGNL,安全;但一旦进入${},就彻底脱离预编译保护- 日志里查不到异常,因为SQL语法合法,只是逻辑被篡改
哪些场景必须用${}?又该怎么控住风险
只有三类场景无法避免${}:动态表名、动态列名、动态ORDER BY / GROUP BY字段。它们不能走#{},因为JDBC预编译不支持占位符替代语法结构。
- 表名:
SELECT * FROM ${tableName}→ 必须白名单校验,如test="tableName == 'user' || tableName == 'order'" - 排序字段:
ORDER BY ${sortField}→ 只允许name、created_time等固定值,禁止任意字符串 - IN子句的字段名(极少见):
WHERE ${colName} IN (...)→ 同样需白名单,且colName不能是用户可控输入
关键点:${}前必须有<if test="..."></if>做硬性过滤,而不是仅靠Java层校验——因为XML里的test是运行时执行的,能拦住非法值进入SQL拼接阶段。
用替代多个能降低风险吗
不能自动降低注入风险,但能提升可维护性和校验严谨性。例如排序逻辑:
<choose><when test="sortField == 'name' and sortOrder == 'asc'">ORDER BY name ASC</when><when test="sortField == 'name' and sortOrder == 'desc'">ORDER BY name DESC</when><when test="sortField == 'age' and sortOrder == 'asc'">ORDER BY age ASC</when><otherwise>ORDER BY id DESC</otherwise></choose>
这种写法天然排除了非法字段和非法顺序组合,比分散的<otherwise></otherwise>里如果还用了${},照样危险。
- 错误:
<otherwise>ORDER BY ${sortField}</otherwise>—— 绕过所有校验 - 正确:
<otherwise>ORDER BY id DESC</otherwise>—— 固定回退值,无变量
最容易被忽略的坑:模糊查询和IN子句的“伪安全”写法
很多团队以为把LIKE '%${title}%'改成LIKE CONCAT('%', #{title}, '%')就安全了,其实不然:
- 如果
#{title}是用户输入,CONCAT函数本身没问题,但若前端传入title=abc%27 OR 1=1 --,解码后仍是abc' OR 1=1 --,而#{}会把它当字符串字面量处理,不会触发注入——这部分其实是安全的 - 真正危险的是
IN子句写成id IN (${ids}),哪怕加了判断 ids != null,只要${ids}是逗号分隔字符串(如"1,2,3; DROP TABLE x; --"),就会直接执行 - 正确解法是用
<foreach></foreach>配合#{}:id IN <foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>
复杂点在于:同一个











