本系列首篇,從最基礎的查詢構建過程切入,還原 SQL 注入產生的根本原因——「數據與代碼未分離」,為後續防禦方案奠定堅實認知基礎。
PHP 以低門檻、高效率著稱,讓新手也能快速構建動態站點。但這份便利往往掩蓋了安全陷阱:當開發者習慣將用戶輸入直接拼接進 SQL 字符串,便為惡意數據打開了大門。
本質上,注入發生在應用程序將不可信輸入作為代碼片段執行時。數據庫本意是存取數據,但當查詢構建邏輯允許外部數據改變 SQL 語義結構,攻擊者便能注入任意指令——讀取、篡改、刪除,甚至執行管理操作。
以葡萄酒查詢為例,正常流程:
variety=lagrein$variety = $_POST['variety']; // 值為 "lagrein"$query = "SELECT * FROM wines WHERE variety='$variety'";SELECT * FROM wines WHERE variety='lagrein' ✅ 正常執行但查詢實際構建過程是:
$query = "SELECT * FROM wines WHERE variety = '"; // 固定部分,單引號開啟
$query .= $variety; // 用戶輸入直接拼接
$query .= "'"; // 單引號閉合
提交 variety=lagrein' OR 1=1;--,拼接後變為:
SELECT * FROM wines WHERE variety = 'lagrein' OR 1=1;--'
單引號提前閉合,OR 1=1 恆真條件生效,-- 註釋掉尾部單引號。結果:返回 wines 表所有記錄。
SQL 解析器無法區分「開發者寫的代碼」與「用戶傳的數據」。在字符串拼接模式下,用戶輸入中的特殊字符(單引號、分號、註釋符)會被解析器視為 SQL 語法元素,從而改變查詢語義。
1 OR 1=1 完全不需要特殊字符。參數化查詢(預處理)通過預先編譯 SQL 模板,再綁定參數值,確保數據永遠不會被解析為 SQL 代碼:
// PDO 範例
$stmt = $pdo->prepare("SELECT * FROM wines WHERE variety = ?");
$stmt->execute([$variety]); // $variety 純粹作為數據值傳遞,無論內容如何都不改變 SQL 結構
掌握原理,方能不被工具束縛;堅持參數化,方能高枕無憂。