SQL 注入仍是 Web 應用頭號安全威脅。本文系統梳理注入分類、多查詢攻擊向量、經典漏洞案例(Invision Power Board),並提供基於 PHP 現代特性的完整防護體系。
-- 原始查詢
SELECT * FROM wines WHERE variety = 'lagrein'
-- 注入 1=1 導致全表掃描
SELECT * FROM wines WHERE variety = 'lagrein' OR 1=1
後果:繞過認證、洩露敏感資料、推斷資料庫結構。
UPDATE wines SET type='red', vintage='9999' WHERE variety = 'lagrein' OR 1=1
全表資料被惡意修改,若無備份將造成不可逆損失。
DELETE FROM wines WHERE variety = 'lagrein' OR 1=1
全表清空,災難級後果。
SELECT * FROM wines WHERE variety = 'lagrein'; GRANT ALL ON *.* TO 'BadGuy@%' IDENTIFIED BY 'gotcha';
利用分號終止原查詢,附加惡意指令。MySQL 預設不支援多查詢(mysqli 除非呼叫 mysqli_multi_query),但 SQLite 預設允許,風險極高。
// 登入查詢
$DB->query("SELECT * FROM ibf_members WHERE id=$mid AND password='$pid'");
// 變數來源:Cookie 直接解碼,無任何過濾
$mid = intval($std->my_getcookie('member_id')); // 強制整數,相對安全
$pid = $std->my_getcookie('pass_hash'); // 直接帶入查詢,極度危險
攻擊者構造 Cookie:pass_hash=' OR 1=1 --,即可繞過密碼驗證登入任意帳號。修復方案:所有外部輸入統一經過 clean_value() 淨化。
<?php
// PDO 範例
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email AND status = 1");
$stmt->execute([':email' => $userInput]);
$user = $stmt->fetch();
// mysqli 範例
$stmt = $mysqli->prepare("SELECT * FROM users WHERE email = ?");
$stmt->bind_param('s', $userInput);
$stmt->execute();
原理:SQL 結構與資料分離編譯,資料永遠不會被解析為 SQL 指令。
(int)$input 或 filter_var($input, FILTER_VALIDATE_INT)in_array($input, $allowedIds, true)| 錯誤做法 | 風險 | 正確替代 |
|---|---|---|
| addslashes / mysql_real_escape_string | 字元集繞過、不支援多位元組編碼 | 參數化查詢 |
| 手工拼接 SQL 字串 | 極高風險、維護困難 | Query Builder / ORM |
| 信任 $_SERVER / $_COOKIE / HTTP 標頭 | 全可控、易被偽造 | 視同不可信輸入,統一驗證 |
| 錯誤訊息回顯給前端 | 洩露資料庫結構、協助攻擊者調試 | 生產環境關閉 display_errors,記錄至日誌 |
SQL 注入本質是代碼與資料未分離。參數化查詢從語言機制層面徹底解決此問題,配合最小權限與纵深驗證,才能構建真正可信的資料存取層。