SQL 注入 是 Web 應用最嚴重的安全威脅之一。本文系統梳理 7 大類經典注入手法,分析原理與實例,並給出針對性防禦方案。
原理: 用戶輸入直接拼接進 SQL 字符串,單引號閉合前級查詢,注入任意邏輯。
脆弱代碼:
"SELECT * FROM users WHERE name = '" + userName + "';"
攻擊載荷:
輸入:a' OR 't'='t
SQL:SELECT * FROM users WHERE name = 'a' OR 't'='t'; -- 恆真,繞過認證
多語句注入(支持批量查詢的 DB):
輸入:a'; DROP TABLE users; SELECT * FROM data WHERE name LIKE '%
SQL:SELECT * FROM users WHERE name = 'a'; DROP TABLE users; SELECT * FROM data WHERE name LIKE '%';
原理: 數值型參數未驗證類型,直接拼接,攻擊者傳入 SQL 片段而非數字。
脆弱代碼:
"SELECT * FROM data WHERE id = " + a_variable + ";"
攻擊載荷:
輸入:1; DROP TABLE users
SQL:SELECT * FROM data WHERE id = 1; DROP TABLE users;
案例: MySQL mysql_real_escape_string() 在特定字符集(如 GBK)下編碼繞過漏洞(CVE-2008-2079)。
防禦: 及時更新數據庫版本,使用預處理語句繞過驅動層轉義邏輯。
原理: 頁面不直接回顯數據或錯誤,但根據邏輯判斷結果返回不同內容(如「文章存在」vs「不存在」)。
攻擊過程: 逐位暴力猜解數據(如密碼雜湊),極其耗時。工具 sqlmap、Absinthe 可自動化利用。
原理: 構造邏輯判斷,觀察頁面差異。
正常:SELECT * FROM books WHERE id = 'OOk14cd' AND 1=1 -- 返回內容
注入:SELECT * FROM books WHERE id = 'OOk14cd' AND 1=2 -- 返回空/不同內容
→ 證明注入存在,可進一步構造子查詢竊取數據
原理: 強制數據庫在條件為真時產生錯誤,通過錯誤回顯提取信息。
SELECT 1/0 FROM users WHERE username='Ralph' -- 若用戶存在,除零錯誤
SELECT CASE WHEN (SELECT 1 FROM users WHERE username='admin') THEN 1/0 ELSE 1 END -- 通過錯誤判斷用戶存在
原理: 利用 SLEEP()、BENCHMARK() 等函數,根據條件真偽延遲響應,通過測量時間差推斷信息。
MySQL: SELECT IF(1=1, SLEEP(5), 0) -- 5秒延遲
PostgreSQL: SELECT pg_sleep(5)
SQL Server: WAITFOR DELAY '0:0:5'
Oracle: DBMS_PIPE.RECEIVE_MESSAGE('a',5)
現代攻擊高度自動化:
查詢:SELECT * FROM users WHERE username = '{username}' AND password = '{password}'
輸入密碼:' OR ''='
結果:SELECT * FROM users WHERE username = 'anyuser' AND password = '' OR ''=''
→ 條件恆真,返回首行用戶(通常是管理員)
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
]);
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = ? AND password = ?');
$stmt->execute([$_POST['username'], $_POST['password']]); // 密碼應為雜湊值驗證
$user = $stmt->fetch();
?>
<?php
function quote_smart($value, PDO $pdo): string {
// 1. 若開啟 magic_quotes_gpc(PHP<5.4),先去轉義
if (get_magic_quotes_gpc()) {
$value = stripslashes($value);
}
// 2. 非數值類型,使用驅動層真實轉義並加引號
if (!is_numeric($value)) {
return "'" . $pdo->quote($value) . "'"; // PDO::quote 內部調用 mysql_real_escape_string
}
return $value;
}
// 使用示例(僅用於無法立即改造的舊代碼)
$sql = "SELECT * FROM users WHERE id = " . quote_smart($_GET['id'], $pdo);
?>
⚠️ 注意: quote_smart 自動加引號,調用時勿重複添加。遠程連接必須傳入 PDO 實例作為第二參數。
| 層級 | 措施 | 說明 |
|---|---|---|
| 代碼層 | 全面使用預處理語句 | 徹底分離代碼與數據,根治注入 |
| 代碼層 | 嚴格輸入驗證(類型、長度、格式、白名單) | 業務邏輯約束,減少攻擊面 |
| 數據庫層 | 最小權限賬號 | Web 賬號僅擁有必要表的增刪改查 |
| 數據庫層 | 禁用多語句執行 | PDO::MYSQL_ATTR_MULTI_STATEMENTS = false |
| 網絡層 | WAF(Web 應用防火牆) | 識別攔截常見攻擊特征,纵深防禦 |
| 監控層 | SQL 審計日誌 + 異常告警 | 事後追蹤,發現 0day 利用 |
| 開發層 | 代碼審計 + SAST 掃描 | 上線前發現漏洞 |
SQL 注入本質是**代碼與數據未分離**。無論攻擊手法如何變換(聯合查詢、盲注、報錯、延遲),**預處理語句**始終是根本解決方案。輔以嚴格輸入驗證、最小權限、WAF 纵深防禦,可將風險降至最低。開發團隊應建立「無預處理不提交」的工程規範,定期掃描歷史代碼遷移。