SQL 注入是 Web 應用最常見的安全漏洞之一。攻擊者通過在表單或 URL 參數中植入惡意 SQL 片段(如 or '1'='1'),繞過業務邏輯竊取或破壞數據。銀行級系統常用存儲過程徹底隔離 SQL,但日常開發中,正確使用 MyBatis 的參數綁定機制即可有效防禦。
| 語法 | 含義 | 是否預編譯 | 適用場景 |
|---|---|---|---|
#{param} | 佔位符參數 | ✅ 是 | 所有動態值(WHERE 條件、INSERT 值等) |
${param} | 字符串直接拼接 | ❌ 否 | 表名、列名、排序字段等無法預編譯的標識符 |
<select id="getBlogById" resultType="Blog" parameterType="int">
select id, title, author, content from blog where id = #{id}
</select>
執行時 MyBatis 將 SQL 發送給數據庫預編譯:
select id, title, author, content from blog where id = ?
隨後僅傳遞參數值替換 ?。因為 SQL 結構在編譯階段已固化,攻擊者輸入的任何內容都會被視為普通字符串而非 SQL 片段,從而徹底杜絕注入。
底層原理:MyBatis 內部使用 JDBC PreparedStatement,其 setXxx() 方法會自動處理轉義與類型轉換。
<select id="orderBlog" resultType="Blog" parameterType="map">
select id, title, author, content from blog order by ${orderParam}
</select>
若 orderParam = "id; DROP TABLE blog;--",最終 SQL 變為:
select id, title, author, content from blog order by id; DROP TABLE blog;--
防禦措施:
1. 白名單校驗:在 Mapper 接口或 Service 層對 orderParam 嚴格匹配允許的列名集合。
2. 工具類轉義:引入 SQLUtils 或自寫正則過濾特殊字符。
3. 原則:能用 #{ } 絕不使用 ${ },只有標識符(表/列名)無法預編譯時才被迫使用 ${ } 並務必做嚴格校驗。
PreparedStatement 實現 SQL 預編譯,#{ } 是防注入的基石。${ } 會直接拼接 SQL,極易導致注入,僅限於動態表名/列名/排序字段等場景,並配合白名單校驗。