flawopen.com/Java 中的 SQL Injection/MyBatis
MyBatis ${} 与 #{}:防止 SQL Injection
参考页面
简答
把 #{value} 想成预处理语句中的安全值槽:数据库把它当作数据。${value} 会在预处理前把文本复制进 SQL。
绑定值与原始 SQL 文本
不安全 — ${} 原始文本
<select id="getUser">
SELECT * FROM users
WHERE id = ${id}
</select>
// Raw substitution: the SQL text changes
绑定值 — #{}
<select id="getUser">
SELECT * FROM users
WHERE id = #{id}
</select>
// MyBatis creates a PreparedStatement parameter
MyBatis 为什么提供两种语法
#{} 使用 JDBC PreparedStatement 参数绑定值。参数不能替代表名、列名或排序方向等 SQL 语法。${} 会直接替换文本且不转义。优先使用静态语句或 <choose> 分支;否则从服务端白名单选择完整标识符,绝不插入任意请求文本。
必须使用动态标识符时
对白名单中的完整 SQL 标识符或片段进行选择
绑定参数不能替代标识符。排序时将用户选择映射为服务端固定 SQL 片段(或使用 <choose>)。不要把客户端列名、表名或 ASC/DESC 直接传给 ${}。转义不能使任意 SQL 片段安全。
如何审查 MyBatis mapper
rg -n -F '${' --glob '*.xml' --glob '*.java' .检查 XML 和注解中的匹配项并追踪值的来源。值用 #{};${} 必须来自服务端有限白名单。搜索只能找到语法,无法发现所有间接 SQL 构造。
常见问题
注解式 mapper 也适用吗?
适用。注解使用相同规则。@Select 中的 ${column} 插入原始文本;值用 #{value} 绑定,标识符须白名单。
搜索 ${} 就能证明代码安全吗?
不能。检查搜索结果;SQL 可能在别处拼接。为 XML 和注解增加检查,并测试排序选项只映射到预期固定片段。