當使用者輸入包含單引號 (`'`)、雙引號 (`"`)、管線符 (`|`)、連字號 (`&`)、分號 (`;`) 等字元,直接拼接至 SQL 字串會導致:
| 分類 | 型別 | 範圍/長度 | 儲存 | 備註 |
|---|---|---|---|---|
| 精確數值 | BIT | 0/1/NULL | 1 bit (8欄=1 byte) | 布林旗標 |
| TINYINT | 0~255 | 1 byte | 非負小整數 | |
| SMALLINT | -32,768~32,767 | 2 bytes | ||
| INT | -2,147,483,648~2,147,483,647 | 4 bytes | 最常用整數 | |
| BIGINT | -2^63~2^63-1 | 8 bytes | 大整數 | |
| 精確十進位 | DECIMAL(p,s) / NUMERIC(p,s) | p=1~38, s=0~p | 5~17 bytes | 金額、精確計算首選 |
| MONEY / SMALLMONEY | ±922 兆 / ±21 萬 | 8/4 bytes | 貨幣專用、隱含 4 位小數 | |
| 近似數值 | FLOAT(n) | -1.79E+308~1.79E+308 | 4/8 bytes | 科學計算、不精確 |
| REAL | -3.40E+38~3.40E+38 | 4 bytes | FLOAT(24) | |
| 日期時間 | DATETIME | 1753-01-01~9999-12-31 | 8 bytes | 精度 3.33 ms |
| SMALLDATETIME | 1900-01-01~2079-06-06 | 4 bytes | 精度 1 分鐘 | |
| DATETIME2(n) | 0001-01-01~9999-12-31 | 6~8 bytes | 精度 100ns、SQL 2008+ 推薦 | |
| DATE | 0001-01-01~9999-12-31 | 3 bytes | 僅日期、SQL 2008+ | |
| TIME(n) | 00:00:00~23:59:59.9999999 | 3~5 bytes | 僅時間、SQL 2008+ | |
| DATETIMEOFFSET(n) | 含時區 | 8~10 bytes | 全球化應用 | |
| 字串 (非 Unicode) | CHAR(n) | 1~8,000 定長 | n bytes | 固定長度、填充空白 |
| VARCHAR(n) | 1~8,000 變長 | 實際長度+2 bytes | 通用字串 | |
| VARCHAR(MAX) | 2^31-1 bytes (2 GB) | 溢位儲存 | 大型文字、取代 TEXT | |
| 字串 (Unicode) | NCHAR(n) | 1~4,000 定長 | 2n bytes | 固定長度、多語系 |
| NVARCHAR(n) | 1~4,000 變長 | 2×實際長度+2 | 多語系通用 | |
| NVARCHAR(MAX) | 2^30-1 字元 (1 GB) | 溢位儲存 | 大型 Unicode、取代 NTEXT | |
| 二進位 | BINARY(n) | 1~8,000 定長 | n bytes | |
| VARBINARY(n) | 1~8,000 變長 | 實際+2 bytes | ||
| VARBINARY(MAX) | 2 GB | 溢位 | 取代 IMAGE | |
| 特殊 | UNIQUEIDENTIFIER | 16 bytes (GUID) | 16 bytes | NEWID()/NEWSEQUENTIALID() |
| 特殊 | TIMESTAMP / ROWVERSION | 8 bytes 遞增 | 8 bytes | 樂觀鎖、版本戳、非日期 |
| 特殊 | CURSOR | 游標參考 | 僅變數/參數、不可建表 | |
| 特殊 | TABLE | 表值參數 | 預存程序參數、UDT | |
| 特殊 | XML | 2 GB | 溢位 | 結構化文件、支援索引/XQuery |
Function SQLFixup(TextIn)
SQLFixup = Replace(TextIn, "'", "''") ' 單引號轉義
End Function
' 使用
SQL = "SELECT * FROM Users WHERE Name = '" & SQLFixup(txtName) & "'"
缺陷: 僅處理單引號,無法防禦 `--`、`;`、`/* */`、Unicode 繞過、二階注入;維護成本高、易遺漏。
-- ADO.NET (C#)
using (var cmd = new SqlCommand("SELECT * FROM Users WHERE Name = @Name", conn))
{
cmd.Parameters.Add("@Name", SqlDbType.NVarChar, 100).Value = txtName.Text;
using (var rd = cmd.ExecuteReader()) { ... }
}
-- ADO Classic (ASP/VBScript) - 亦支援參數化
Set cmd = Server.CreateObject("ADODB.Command")
cmd.ActiveConnection = conn
cmd.CommandText = "SELECT * FROM Users WHERE Name = ?"
cmd.Parameters.Append cmd.CreateParameter("@Name", 202, 1, 100, txtName) ' adVarWChar
Set rs = cmd.Execute
-- 預存程序內動態 SQL
CREATE PROC usp_Search @Name NVARCHAR(100), @SortCol SYSNAME
AS
BEGIN
DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM Users WHERE Name = @Name';
-- 排序欄位白名單驗證,不可參數化
IF @SortCol NOT IN (N'Name', N'CreateDate', N'Status') SET @SortCol = N'Name';
SET @sql += N' ORDER BY ' + QUOTENAME(@SortCol);
EXEC sp_executesql @sql, N'@Name NVARCHAR(100)', @Name = @Name;
END
-- 隱性轉換導致索引失效
WHERE VarcharCol = 123 -- 字串隱性轉數字,掃描全表
WHERE IntCol = '123' -- 數字隱性轉字串,可能求值
-- 正確:顯性轉換或參數型別匹配
WHERE VarcharCol = CAST(123 AS VARCHAR(10))
WHERE IntCol = 123
-- LIKE 萬用字元轉義
WHERE Name LIKE '%[' + @Search + '%' ESCAPE '\' -- 使用 ESCAPE 字元
-- 或動態建構
DECLARE @pattern NVARCHAR(100) = REPLACE(REPLACE(@Search, '[', '[[]'), '%', '[%]');
WHERE Name LIKE @pattern ESCAPE '\';