SQL 注入本質是「資料被當成指令執行」。核心防禦原則:**永遠不拼接 SQL 字串,改用參數化查詢**。
-- 應用拼接
sql = "SELECT * FROM users WHERE name='" + txtName.Text + "' AND pwd='" + txtPwd.Text + "'";
-- 攻擊輸入:txtName = "admin' --"
-- 實際執行:SELECT * FROM users WHERE name='admin' --' AND pwd='...'
-- 註解掉密碼檢查,直接以 admin 登入
-- C# ADO.NET
string sql = "SELECT * FROM users WHERE username=@user AND password=@pwd";
using (var cmd = new SqlCommand(sql, conn)) {
cmd.Parameters.AddWithValue("@user", txtName.Text);
cmd.Parameters.AddWithValue("@pwd", txtPwd.Text);
// 參數在引擎層綁定,永遠不會被解析為 SQL 指令
}
-- Java JDBC
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE username=? AND password=?");
ps.setString(1, name); ps.setString(2, pwd);
-- PHP PDO
$stmt = $pdo->prepare("SELECT * FROM users WHERE username=? AND password=?");
$stmt->execute([$name, $pwd]);
CREATE PROC sp_login @user NVARCHAR(50), @pwd NVARCHAR(50)
AS SELECT * FROM users WHERE username=@user AND password=@pwd;
-- 應用呼叫 EXEC sp_login @user, @pwd
WhereRaw、DB::raw 等繞過機制。Regex.IsMatch(input, "^[a-zA-Z0-9_]{3,20}$")。MaxLength、StringLength 屬性。mysqli_real_escape_string / PDO::quote,但易漏、不支援所有語境。| 誤區 | 真相 |
|---|---|
| 「我過濾了單引號就安全」 | 編碼繞過、二次注入、數值型注入 (id=1 OR 1=1) 完全繞過 |
| 「存儲過程絕對安全」 | 存儲過程內部若用動態 SQL 拼接 (EXEC @sql) 同樣可被注入 |
| 「只有 Web 表單會被注入」 | API、爬蟲回調、日誌解析、ETL 匯入、內部工具皆為入口 |
將「參數化查詢」寫入團隊編碼規範、Code Review 必檢項、CI 掃描規則 (SonarQube/Semgrep),從源頭杜絕 SQL 注入。