SQL 注入是危害最高的 Web 漏洞之一,但防禦相對明確。本文從漏洞成因、核心防禦原理、框架層配置、輸入驗證、編碼函數兜底五個維度構建多層防護體系。
-- 正常 SELECT id, no FROM user WHERE id = 2; -- 注入:id = "2 OR 1=1" SELECT id, no FROM user WHERE id = 2 OR 1=1; -- 返回全表 -- 危險:id = "2; DROP TABLE sqlinject;" -- 多語句執行導致表被刪
本質:將用戶輸入的數據拼接為 SQL 命令執行,導致數據與代碼邊界模糊。
"SELECT * FROM t WHERE id=" + userInputOR、UNION、DROP)被解析為命令而非字面值。// JDBC 標準寫法 String sql = "SELECT id, no FROM user WHERE id = ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, userId); // 綁定變量 ResultSet rs = ps.executeQuery();
prepareStatement 階段完成 SQL 解析、語法樹構建、執行計劃生成,結構已固化。setXxx 傳入的參數只作為字面值參與執行,不再經過語法分析,其中的 SQL 關鍵字失去特殊含義。setInt/setString 強制類型檢查,進一步收縮攻擊面。| 框架 | 安全寫法 | 危險寫法 | 檢查要點 |
|---|---|---|---|
| MyBatis | #{param} | ${param} | 全局搜索 ${,僅用於表名/列名/排序列等不可參數化場景 |
| Hibernate/JPA | HQL/JPQL 參數綁定 :param | 字符串拼接 HQL | 避免 createQuery("... WHERE id=" + id) |
| Spring JdbcTemplate | query("... WHERE id=?", new Object[]{id}) | 拼接 SQL 字符串 | 統一使用 NamedParameterJdbcTemplate |
| MyBatis-Plus | Wrapper 條件構造器 | 自定義 SQL 片段拼接 | 審計 @Select 註解中的拼接 |
^\d+$ 或 Integer.parseInt 異常捕獲。IN ('A','B','C') 白名單校驗。Map 白名單轉換,絕不直接拼接。// ESAPI 範例 MySQLCodec codec = new MySQLCodec(Mode.STANDARD); String safeName = ESAPI.encoder().encodeForSQL(codec, userName); String sql = "SELECT * FROM user WHERE name = '" + safeName + "'";
DROP/TRUNCATE/CREATE/FILE/SHUTDOWN 權限。SELECT/INSERT/UPDATE/DELETE,且限定具體庫/表。+/concat/||)+ 用戶輸入變量。${}、Hibernate 拼接、JPA 原生 SQL。