什麼是 SQL Injection 以及如何防止?
實務分析 SQL injection 攻擊、真實案例,以及在生產程式碼中確實有效的修復方法。
SQL injection 自 1990 年代後期以來就存在,至今仍然是 web 應用程式遭到入侵最常見的方式之一。這個漏洞很簡單:攻擊者傳送的輸入被解釋成 SQL 程式碼而不是純粹的資料,導致資料庫執行它從來不應該做的事。
攻擊如何實際運作
假設一個登入表單在 PHP 中建立查詢如下:
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
如果應用程式沒有清理輸入,攻擊者在使用者名稱欄位輸入 admin' --。查詢變成:
SELECT * FROM users WHERE username = 'admin' --' AND password = ''
-- 註釋掉了這一行的其餘部分,所以密碼檢查永遠不會執行。這是典型的認證繞過。攻擊者也會使用 UNION SELECT 從其他表格提取資料,或使用分號堆疊查詢來執行具破壞性的操作如 DROP TABLE users;(如果驅動程式允許多個陳述式)。
還有盲 SQL injection,其中應用程式不會直接顯示查詢結果。攻擊者透過計時推斷資訊(MySQL 中的 SLEEP(5))或布林值回應(真假條件下頁面表現不同)。像 sqlmap 這樣的工具會在找到易受攻擊的參數後自動化這種資料提取。
為什麼字串串聯是根本問題
這類攻擊的每個變體都回到一件事:在同一個字串中混合程式碼和資料。資料庫無法區分合法值和注入的語法,因為它們透過同一個通道送來。轉義引號在某些情況下有幫助,但很脆弱——不同的編碼、二階注入(資料儲存一次,然後稍後不安全地重複使用)以及驅動程式特定的怪癖都會產生繞過路徑。轉義是補丁,不是修復。
參數化查詢是真正的修復
修復是在驅動程式層級將 SQL 程式碼和使用者資料分開,使用參數化查詢(也稱為準備好的陳述式)。資料庫先收到查詢結構,然後才綁定值,所以使用者輸入永遠無法改變查詢的意思。
使用 psycopg2 的 Python:
cur.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))
使用 mysql2 的 Node.js:
connection.execute('SELECT * FROM users WHERE username = ? AND password = ?', [username, password]);
使用 JDBC 的 Java:
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE username = ? AND password = ?");
stmt.setString(1, username);
stmt.setString(2, password);
注意這個模式:預留位置(%s、?)為資料保留位置,實際的值分別傳遞。沒有字串串聯,不需要手動轉義。這適用於在一般應用程式中你會撰寫的絕大多數查詢。
動態表格或欄位名稱呢?
參數化處理的是值而不是識別符——你無法將表格名稱作為參數綁定。如果你的應用程式需要動態選擇表格(罕見,且通常是設計味道),應該針對一個硬編碼清單將允許的值列入白名單,而不是直接信任使用者輸入:
allowed_tables = {'orders', 'invoices', 'customers'}
if table_name not in allowed_tables:
raise ValueError("Invalid table")
永遠不要透過來自使用者輸入的字串格式化建立識別符名稱,即使有轉義也不行。
查詢本身以外的分層防禦
參數化查詢是主要控制,但還有幾個事項很重要:
- 資料庫帳戶的最小權限。 應用程式的資料庫使用者不應該有
DROP、ALTER或對無關結構描述的存取權限。如果注入確實滑了過去,受限的權限會限制損害。 - ORM 預設有幫助。 Django 的 ORM、SQLAlchemy 和 Hibernate 在你使用它們的標準查詢構建方法時都會自動參數化查詢。當開發人員掉入原始 SQL 或使用字串內插的
.extra()/text()呼叫時,風險重新出現——所以特別審計這些位置。 - 輸入驗證是次要層,不是替代品。 檢查電子郵件欄位看起來像電子郵件是好的做法,但它本身無法阻止注入——攻擊者會找到仍能通過寬鬆驗證的創意酬載。
- WAF 可以捕捉已知攻擊模式,但它是偵測層,不是底層程式碼的修復。
測試你自己的程式碼找到這個漏洞
執行你的查詢透過靜態分析工具(Python 的 Bandit、帶有 SQL injection 規則集的 Semgrep)作為 CI 的一部分。手動測試時,試著將單引號(')注入每個輸入欄位,並注意 SQL 錯誤訊息是否在回應中洩漏——這通常是查詢未參數化的第一個跡象。
如果你想更深入了解這個主題,Korra Studio 的 Web Security 課程涵蓋注入以及 XSS 和認證繞過,而 Databases 課程會講解避免此類漏洞的查詢設計模式。
本文由 AI 協助撰寫,經 Michal Pilch(CISSP)審核並發佈,Korra Studio。
這是 Korra Studio 知識庫中的一篇筆記——該平台將每個主題與一對一的師資配對。
免費開始arrow_forward