現代網頁應用程式的 SQL 注入防範
SQL 注入仍然是最關鍵的網頁應用程式漏洞之一。攻擊者利用它來竊取資料、繞過身分驗證,甚至執行系統命令。如果你的應用程式透過串接使用者輸入來建構 SQL 查詢,你就處於風險之中。本文說明 SQL 注入的運作方式,並提供在現代網頁應用程式中防範的實用步驟。
SQL 注入如何發生
當不受信任的資料被解譯為 SQL 命令的一部分時,就會發生 SQL 注入。例如,考慮一個使用以下查詢檢查憑證的登入表單:
SELECT * FROM users WHERE username = '$username' AND password = '$password';
如果攻擊者輸入 ' OR '1'='1 作為使用者名稱,並輸入任意密碼,查詢會變成:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'anything';
這會傳回所有使用者,繞過身分驗證。類似的技術可以擷取資料、修改記錄或刪除資料表。
1. 使用預備陳述式(參數化查詢)
預備陳述式將 SQL 程式碼與資料分離。資料庫先接收查詢結構,然後接收參數,因此使用者輸入永遠不會被視為 SQL 程式碼。這是最有效的防禦措施。
PHP 搭配 PDO 的範例:
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $userInput]);
$user = $stmt->fetch();
Java 搭配 JDBC 的範例:
String sql = "SELECT * FROM users WHERE email = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, userInput);
ResultSet rs = stmt.executeQuery();
對於所有使用者提供的資料,包括搜尋欄位、篩選條件和排序參數,都應一律使用參數化查詢。
2. 驗證與清理輸入
雖然預備陳述式是主要防禦,但輸入驗證可增加縱深防禦。驗證輸入是否符合預期模式(例如電子郵件格式、數字 ID),並拒絕任何非預期的內容。例如,如果 ID 應該是整數,請在程式碼中將其轉型為 int。避免使用引號等字元的黑名單——攻擊者可以繞過這類篩選。
3. 安全地使用 ORM
Hibernate、Entity Framework 和 Sequelize 等 ORM 通常預設使用參數化查詢。然而,它們經常允許原始 SQL 片段。對於接受原始字串的方法要謹慎:
- 在 Sequelize 中,避免使用
sequelize.query('SELECT * FROM users WHERE name = \'' + name + '\'')。請改用 replacements 或綁定參數。 - 在 Hibernate 中,請在 HQL 中使用具名參數,而非字串串接。
請務必查閱你的 ORM 文件,了解安全的查詢建構方式。
4. 若無法使用預備陳述式,請對資料進行跳脫
在極少數情況下,當你必須建構動態 SQL(例如動態資料表名稱)時,請使用資料庫驅動程式的跳脫函式。對於 MySQL,mysqli_real_escape_string() 會跳脫特殊字元。但請記住:跳脫不如預備陳述式穩健,應作為最後手段。
5. 對資料庫帳戶套用最小權限原則
不要以 root 或具有完整權限的使用者身分連線至資料庫。為你的應用程式建立專用的資料庫使用者,僅賦予必要的權限(特定資料表上的 SELECT、INSERT、UPDATE、DELETE)。這可在發生注入時限制損害。
6. 使用網頁應用程式防火牆(WAF)
WAF 可以偵測並封鎖常見的 SQL 注入模式。雖然不能取代安全編碼,但它提供了額外的一層防護。許多雲端供應商提供易於啟用的受管 WAF。
7. 定期進行安全測試
使用自動化掃描器或手動滲透測試,來測試你的應用程式是否有 SQL 注入漏洞。SQLMap 等工具可協助識別問題。將安全測試整合至你的 CI/CD 流程中,以便及早發現回歸問題。
防範技術比較
| 技術 | 有效性 | 實作難易度 |
|---|---|---|
| 預備陳述式 | 高 | 容易(多數驅動程式內建) |
| 輸入驗證 | 中 | 中等 |
| 安全使用 ORM | 高 | 若有意識則容易 |
| 跳脫 | 中 | 容易但易出錯 |
| 最小權限 | 中 | 容易 |
| WAF | 低至中 | 容易(受管) |
常見問題
防範 SQL 注入最有效的方法是什麼?
使用預備陳述式搭配參數化查詢是最有效的方法。它確保使用者輸入永遠不會被解譯為 SQL 程式碼。
僅靠輸入驗證能防範 SQL 注入嗎?
不能。輸入驗證是良好的縱深防禦措施,但不應僅依賴它。攻擊者有時可以繞過驗證規則,因此請一律使用預備陳述式作為主要防禦。
ORM 是否自動對 SQL 注入免疫?
ORM 在正確使用時通常是安全的,但它們經常允許原始 SQL 查詢,若你串接使用者輸入,就可能存在漏洞。即使在 ORM 方法中,也請一律使用參數化查詢。
如需額外的安全性,可考慮使用 JSON formatter 來安全地檢查和驗證 API 回應,而不會執行惡意程式碼。