SQL 注入是 Web 應用最頂級的安全風險之一。本文通過實例拆解注入原理,演示字符串拼接的危害,並給出 ADO.NET / mysqli / PDO 參數化查詢的統一防禦模式。
典型登錄查詢(字符串拼接):
string strSql = "SELECT * FROM Users WHERE UserName='" + txtUser.Text
+ "' AND UserPass='" + txtPass.Text + "'";
攻擊者在用戶名輸入:1' OR '1'='1 --
生成 SQL:
SELECT * FROM Users WHERE UserName='1' OR '1'='1 --' AND UserPass='...'
-- 註釋掉後續條件,'1'='1' 永真,繞過驗證並返回首行數據(通常是管理員)。
核心原則:代碼與數據分離。參數不作為 SQL 文本拼接,而是作為獨立對象傳遞給資料庫引擎解析,徹底消除注入面。
using (var cn = new SqlConnection(connStr))
using (var cmd = cn.CreateCommand()) {
cmd.CommandText = "SELECT * FROM Users WHERE UserName=@UserName AND UserPass=@UserPass";
cmd.Parameters.AddWithValue("@UserName", txtUser.Text);
cmd.Parameters.AddWithValue("@UserPass", txtPass.Text);
cn.Open();
using (var rd = cmd.ExecuteReader()) { /* ... */ }
}
$stmt = $mysqli->prepare("SELECT * FROM Users WHERE UserName=? AND UserPass=?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$result = $stmt->get_result();
$stmt = $pdo->prepare("SELECT * FROM Users WHERE UserName=:u AND UserPass=:p");
$stmt->execute([':u' => $username, ':p' => $password]);
$user = $stmt->fetch();
| 措施 | 說明 | 局限性 |
|---|---|---|
| 輸入長度限制 | maxlength、服務端長度校驗 | 無法阻擋短 Payload 攻擊 |
| 特殊字符轉義/過濾 | mysqli_real_escape_string、WAF 規則 | 編碼繞過、上下文敏感(如 LIKE 子句) |
| 最小權限賬號 | 應用賃戶僅授 SELECT/INSERT/UPDATE,禁 DROP/GRANT | 無法防範數據竊取 |
| 錯誤信息隱藏 | 生產環境關閉詳細 SQL 錯誤回顯 | 不解決根因,僅增加攻擊成本 |
-- 錯誤:動態 SQL 拼接
CREATE PROC GetUser @name VARCHAR(50) AS
EXEC('SELECT * FROM Users WHERE Name=''' + @name + '''');
-- 正確:參數化
CREATE PROC GetUser @name VARCHAR(50) AS
SELECT * FROM Users WHERE Name = @name;
+ . || 等拼接操作符出現在 SQL 字符串中。參數化查詢不僅安全,還能利用執行計劃緩存提升效能。將其作為團隊編碼規範的強制項,是構建安全應用的基石。