SQL 注入仍是 Web 應用頭號安全風險。本文通過登錄、查詢、插入三大場景還原注入原理,並給出從配置加固、代碼重構到企業級防禦的完整解決方案。
原生 SQL 拼接:
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
輸入 xx' OR '1'='1,SQL 變為:
SELECT * FROM users WHERE username = 'xx' OR '1'='1' AND password = '...'
1=1 恆真,繞過驗證。若開發者未給變量加引號(視為數值型),注入載荷變為 11 UNION SELECT * FROM users/*。
搜索框直接拼接 LIKE '%$keyword%',輸入 % 或 _ 即可遍歷全表。PHP 可用 addslashes() 配合 str_replace('%', '\%', $keyword) 緩解,但非根治。
註冊語句 INSERT INTO users(username,password,grade) VALUES('frank','123456','1'),密碼輸入 123456','3')/*,SQL 變為:
INSERT INTO users(username,password,grade) VALUES('frank','123456','3')/*,'1');
成功將權限等級 grade 從 1 竄改為 3。
開啟 magic_quotes_gpc = On 自動轉義單引號,但攻擊者可用 CHAR(39) 繞過,PHP 5.4+ 已移除,不推薦依賴。
$sql = "SELECT * FROM users WHERE username = 'frank'";
$result = mysql_query($sql, $conn);
$row = mysql_fetch_array($result);
if ($row['password'] !== $password) { /* 登錄失敗 */ }
優點:SQL 固定,無拼接風險;缺點:需兩次查詢,舊代碼改造成本高。
原理:SQL 模板與數據分離,數據庫引擎預編譯模板,參數僅作值傳入,徹底杜絕注入。
$sql = "SELECT * FROM users WHERE username = ? AND password = ?";
$pdo = new PDO("mysql:host=localhost;port=3306;dbname=xx", "root", "123456");
$pdo->exec("SET NAMES utf8");
$stmt = $pdo->prepare($sql);
$stmt->execute([$username, $password]);
$result = $stmt->fetch();
if (empty($result)) { /* 登錄失敗 */ }
需 php.ini 開啟 extension=pdo_mysql。
部署網絡/主機型 IDS(如 Snort、ModSecurity),實時分析流量特徵庫,攔截注入攻擊,配合 WAF 形成纵深防禦。
防禦 SQL 注入的核心是「數據與代碼分離」。PDO 預處理是現代 PHP 開發的基礎底線;老項目改造優先修復高風險入口;配合代碼審計、最小權限資料庫帳號、錯誤信息不外露、定期滲透測試,構建全生命週期安全體系。