開發者常以為 `Replace("'", "''")` + 長度限制 = 安全。**錯誤**。
SQL Server 允許用函數構造字串,完全不需單引號:
-- 構造 'chris' (ASCII 碼)
CHAR(0x63) + CHAR(0x68) + CHAR(0x72) + CHAR(0x69) + CHAR(0x73)
-- 等價於
CAST(0x6368726973 AS VARCHAR(20)) -- 十六進位直接轉
-- 或
CONVERT(VARCHAR(20), 0x6368726973)
-- 實戰插入 (無任何引號)
INSERT INTO Users (UserID, UserName, PassWord, Privs)
VALUES (666,
CHAR(0x63)+CHAR(0x68)+CHAR(0x72)+CHAR(0x69)+CHAR(0x73), -- 'chris'
CHAR(0x63)+CHAR(0x68)+CHAR(0x72)+CHAR(0x69)+CHAR(0x73), -- 'chris'
0xFFFF);
**數值型欄位更簡單**:SQL Server 隱式轉換數值 → VARCHAR,直接 `INSERT ... VALUES (667, 123, 123, 0xFFFF)` 即可寫入字串 `'123'`。
見第 9 篇詳細機制。核心:**資料庫讀出的資料若直接拼接 SQL,同樣會注入**。
-- 階段 1: 註冊存入 admin''-- (轉義後)
-- 階段 2: 修改密碼功能讀取 UserName 拼接
UPDATE Users SET Pass='new' WHERE User='admin''--' -- 實際執行 WHERE User='admin'
**防禦**:參數化查詢**無例外**,含讀自資料庫的變數。
誤區:「用了存儲過程就安全」。**關鍵在於應用如何呼叫存儲過程**:
| 呼叫方式 | 代碼範例 | 是否安全 | 原因 |
|---|---|---|---|
| 字串拼接 EXEC | `cmd.CommandText = "EXEC GetUser '" + txtUser.Text + "'";` | **不安全** | 拼接發生在應用層,參數未參數化傳遞 |
| ADO Command + Parameters | `cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.Add("@User", txtUser.Text);` | **安全** | 參數經 TDS 協議以二進位傳遞,不經 SQL 解析器 |
| 存儲過程內部動態 SQL | `CREATE PROC P @x VARCHAR(50) AS EXEC('SELECT * FROM T WHERE c=' + @x)` | **不安全** | 存儲過程內部拼接動態 SQL,參數成為代碼一部分 |
-- 直接在 SSMS 執行
EXEC sp_who '1'; SELECT * FROM sysobjects; -- 批次分隔符 ; 或換行
EXEC sp_who '1' SELECT * FROM sysobjects; -- 同樣執行
任何附加語句在存儲過程返回後仍會執行 (若應用層拼接)。
-- 假設登入代碼: "SELECT * FROM Users WHERE User='" + u + "' AND Pass='" + p + "'"
-- 攻擊者輸入:
User: ' OR 1=1-- -- 繞過登入 (首筆用戶)
User: ' UNION SELECT 1, 'hacker', 'pass', 1-- -- 偽造用戶登入
User: '; INSERT INTO Users VALUES (999, 'hack', 'pass', 1)-- -- 註冊後門帳號
**唯一解**:**全鏈路參數化查詢** + **最小權限資料庫帳號** + **嚴格輸入白名單** + **移除危險擴展預存程序**。