「存儲過程天然防注入」是**危險誤區**。當存儲過程內部使用 `EXEC(@sql)` / `sp_executesql` 執行**字符串拼接**的動態 SQL,且拼接了**未經驗證的用戶輸入**,同樣存在注入風險。本文通過最小化漏洞復現、攻擊向量分析、參數化重構、靜態掃描規則,徹底釐清邊界。
CREATE PROCEDURE dbo.GetPerson
@strWhere VARCHAR(100) = '' -- 用戶輸入:WHERE 子句片段
AS
BEGIN
DECLARE @strSQL VARCHAR(1000) = 'SELECT TOP 10 * FROM Person WHERE 1=1 ';
IF @strWhere != ''
SET @strSQL = @strSQL + @strWhere; -- 直接拼接,無驗證、無參數化
PRINT @strSQL;
EXEC(@strSQL); -- 執行拼接字符串
END;
EXEC dbo.GetPerson @strWhere = ' AND LastName = ''Smith'' ';
生成:`SELECT TOP 10 * FROM Person WHERE 1=1 AND LastName = 'Smith'` ✅
EXEC dbo.GetPerson @strWhere = ' AND 1=1; DROP TABLE Person; -- ';
生成:`SELECT TOP 10 * FROM Person WHERE 1=1 AND 1=1; DROP TABLE Person; --` 💥 **表被刪除**
EXEC dbo.GetPerson @strWhere = ' AND 1=0 UNION SELECT name, password_hash FROM sys.sql_logins; -- ';
生成:`SELECT TOP 10 * FROM Person WHERE 1=1 AND 1=0 UNION SELECT name, password_hash FROM sys.sql_logins; --` 💥 **密碼哈希洩露**
| 模式 | 代碼示例 | 注入風險 | 計劃復用 |
|---|---|---|---|
| 字符串拼接 + EXEC | `EXEC('SELECT * FROM T WHERE c=' + @input)` | ❌ 極高 | 差 (每次不同字符串新計劃) |
| sp_executesql + 參數化 | `EXEC sp_executesql N'SELECT * FROM T WHERE c=@p', N'@p varchar(50)', @input` | ✅ 無 (參數強類型、計劃分離) | 優 (參數化計劃復用) |
| 無動態 SQL (靜態) | `SELECT * FROM T WHERE c=@input` | ✅ 無 | 最優 |
CREATE PROCEDURE dbo.GetPerson_Safe
@LastName NVARCHAR(50) = NULL,
@FirstName NVARCHAR(50) = NULL
AS
BEGIN
DECLARE @sql NVARCHAR(MAX) = N'SELECT TOP 10 * FROM Person WHERE 1=1 ';
DECLARE @params NVARCHAR(MAX) = N'@LastName NVARCHAR(50), @FirstName NVARCHAR(50)';
IF @LastName IS NOT NULL
SET @sql += N' AND LastName = @LastName ';
IF @FirstName IS NOT NULL
SET @sql += N' AND FirstName = @FirstName ';
EXEC sp_executesql @sql, @params, @LastName, @FirstName;
END;
CREATE PROCEDURE dbo.GetPerson_Static
@LastName NVARCHAR(50) = NULL,
@FirstName NVARCHAR(50) = NULL
AS
SELECT TOP 10 * FROM Person
WHERE 1=1
AND (@LastName IS NULL OR LastName = @LastName)
AND (@FirstName IS NULL OR FirstName = @FirstName);
CREATE TYPE dbo.FilterCondition AS TABLE (
ColumnName SYSNAME, Operator VARCHAR(10), Value NVARCHAR(MAX)
);
CREATE PROCEDURE dbo.GetPerson_TVP
@Filters dbo.FilterCondition READONLY
AS
-- 動態構建參數化 SQL,或應用層構建靜態 SQL + TVP 參數值
-- 核心:TVP 內容作為數據,非代碼
-- T-SQL 靜態分析正則 (簡化版) -- 檢測 EXEC(@var) / EXEC(@var + ...) / sp_executesql @sql (無參數定義) -- 禁止模式: EXEC\s*\(\s*@\w+\s*\) EXEC\s*\(\s*@\w+\s*[\+\-]\s* sp_executesql\s+@\w+\s*(?!.*@params) -- 允許模式: sp_executesql\s+N?['"][^'"]*['"]\s*,\s*N?['"]@\w+
存儲過程**不天然防注入**,**動態 SQL 拼接用戶輸入**是注入溫床。鐵律:**用戶輸入永遠作為參數值,絕不作為 SQL 文本片段**。採用 `sp_executesql` 參數化、靜態條件下推、TVP、JSON 參數化,配合靜態掃描門禁,將注入風險壓縮至理論最小。這不是最佳實踐,是**底線合規**。