為什麼在儲存過程中的這個查詢上不會發生 SQL 注入?
我做了以下儲存過程:
ALTER PROCEDURE usp_actorBirthdays (@nameString nvarchar(100), @actorgender nvarchar(100)) AS SELECT ActorDOB, ActorName FROM tblActor WHERE ActorName LIKE '%' + @nameString + '%' AND ActorGender = @actorgender
現在,我嘗試做這樣的事情。也許我做錯了,但我想確保這樣的過程可以防止任何 SQL 注入:
EXEC usp_actorBirthdays 'Tom', 'Male; DROP TABLE tblActor'
下圖顯示了上面的 SQL 在 SSMS 中執行,結果正確顯示,而不是錯誤:
順便說一句,我在執行完查詢後在分號後面添加了該部分。然後我再次執行它,但是當我檢查表 tblActor 是否存在時,它仍然存在。難道我做錯了什麼?或者這真的是防注射的嗎?我想我在這裡也想問的是像這樣安全的儲存過程嗎?謝謝你。
這段程式碼可以正常工作,因為它是:
- 參數化,並且
- 不執行任何動態 SQL
為了使 SQL 注入起作用,您必須建構一個查詢字元串(您沒有這樣做)並且不將單個撇號 (
'
) 轉換為轉義撇號 (''
) (這些通過輸入參數進行轉義)。在您嘗試傳遞“妥協”值時,
'Male; DROP TABLE tblActor'
字元串就是這樣,一個普通的字元串。現在,如果您按照以下方式做某事:
DECLARE @SQL NVARCHAR(MAX); SET @SQL = N'SELECT fields FROM table WHERE field23 = ' + @InputParam; EXEC(@SQL);
那麼這將容易受到 SQL 注入的影響,因為該查詢不在目前的預解析上下文中;該查詢目前只是另一個字元串。因此, 的值
@InputParam
可能是'2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
並且可能會出現問題,因為該查詢將被呈現和執行,如下所示:SELECT fields FROM table WHERE field23 = '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
這是使用儲存過程的(幾個)主要原因之一:本質上更安全(好吧,只要您不通過像我上面顯示的那樣建構查詢而不驗證所使用的任何參數的值來規避這種安全性)。雖然如果您需要建構動態 SQL,首選方法是使用以下參數對其進行參數化
sp_executesql
:DECLARE @SQL NVARCHAR(MAX); SET @SQL = N'SELECT fields FROM table WHERE field23 = @SomeDate_tmp'; EXEC sp_executesql @SQL, N'SomeDate_tmp DATETIME', @SomeDate_tmp = @InputParam;
使用這種方法,嘗試傳遞
'2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
輸入DATETIME
參數的人在執行儲存過程時會出錯。或者即使儲存過程接受@InputParameter
為NVARCHAR(100)
,它也必須轉換為 aDATETIME
才能傳遞給該sp_executesql
呼叫。即使動態 SQL 中的參數是字元串類型,首先進入儲存過程的任何單個撇號都會自動轉義為雙撇號。有一種鮮為人知的攻擊類型,其中攻擊者試圖用撇號填充輸入欄位,這樣儲存過程中的一個字元串將用於構造動態 SQL,但被聲明為太小不能適合所有內容並推出結束撇號並以某種方式以正確數量的撇號結束,以便不再在字元串中“轉義”。這稱為 SQL 截斷,在 Bala Neerumalla 的題為“新 SQL 截斷攻擊和如何避免它們”的 MSDN 雜誌文章中進行了討論,但該文章已不再線上。包含本文的問題 — MSDN 雜誌 2006 年 11 月版— 僅作為 Windows 幫助文件(在**.chm格式)。如果您下載它,它可能由於預設安全設置而無法打開。如果發生這種情況,請右鍵點擊MSDNMagazineNovember2006en-us.chm文件並選擇“屬性”。在其中一個選項卡中,將有一個“信任這種類型的文件”(或類似的文件)選項,需要檢查/啟用。點擊“確定”按鈕,然後再次嘗試打開.chm**文件。
截斷攻擊的另一種變體是,假設使用局部變數來儲存“安全”使用者提供的值,因為它有任何單引號加倍以便被轉義,以填充該局部變數並放置單引號在末尾。這裡的想法是,如果局部變數的大小不合適,最後將沒有足夠的空間容納第二個單引號,讓變數以單引號結尾,然後與單引號組合結束動態 SQL 中的文字值,將結束單引號轉換為嵌入的轉義單引號,然後動態 SQL 中的字元串文字以用於開始下一個字元串文字的下一個單引號結束。例如:
-- Parameters: DECLARE @UserID INT = 37, @NewPassword NVARCHAR(15) = N'Any Value ....''', @OldPassword NVARCHAR(15) = N';Injected SQL--'; -- Stored Proc: DECLARE @SQL NVARCHAR(MAX), @NewPassword_fixed NVARCHAR(15) = REPLACE(@NewPassword, N'''', N''''''), @OldPassword_fixed NVARCHAR(15) = REPLACE(@OldPassword, N'''', N''''''); SELECT @NewPassword AS [@NewPassword], REPLACE(@NewPassword, N'''', N'''''') AS [REPLACE output], @NewPassword_fixed AS [@NewPassword_fixed]; /* @NewPassword REPLACE output @NewPassword_fixed Any Value ....' Any Value ....'' Any Value ....' */ SELECT @OldPassword AS [@OldPassword], REPLACE(@OldPassword, N'''', N'''''') AS [REPLACE output], @OldPassword_fixed AS [@OldPassword_fixed]; /* @OldPassword REPLACE output @OldPassword_fixed ;Injected SQL-- ;Injected SQL-- ;Injected SQL-- */ SET @SQL = N'UPDATE dbo.TableName SET [Password] = N''' + @NewPassword_fixed + N''' WHERE [TableNameID] = ' + CONVERT(NVARCHAR(10), @UserID) + N' AND [Password] = N''' + @OldPassword_fixed + N''';'; SELECT @SQL AS [Injected];
在這裡,要執行的動態 SQL 現在是:
UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';
同樣的動態 SQL,以更易讀的格式,是:
UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N'; Injected SQL--';
解決這個問題很容易。只需執行以下操作之一:
- 除非絕對必要,否則不要使用動態 SQL !(我首先列出這個,因為它確實應該是首先要考慮的)。
- 適當調整局部變數的大小(即應該是輸入參數大小的兩倍,以防傳入的所有字元都是單引號。
- 不要使用局部變數來儲存“固定”值;直接把
REPLACE()
創建的Dynamic SQL放進去:SET @SQL = N'UPDATE dbo.TableName SET [Password] = N''' + REPLACE(@NewPassword, N'''', N'''''') + N''' WHERE [TableNameID] = ' + CONVERT(NVARCHAR(10), @UserID) + N' AND [Password] = N''' + REPLACE(@OldPassword, N'''', N'''''') + N''';'; SELECT @SQL AS [No SQL Injection here];
動態 SQL 不再受到損害:
UPDATE dbo.TableName SET [Password] = N'Any Value ....''' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';
關於上述截斷範例的註釋:
- 是的,這是一個非常人為的例子。僅注入 15 個字元,沒有什麼可以做的。當然,可能具有
DELETE tableName
破壞性,但不太可能添加後門使用者或更改管理員密碼。- 這種類型的攻擊可能需要知道程式碼、表名等。不太可能由隨機的陌生人/腳本小子完成,但我確實在一個被知道漏洞的相當沮喪的前僱員攻擊的地方工作在一個其他人不知道的特定網頁中。這意味著,有時攻擊者確實對系統有深入的了解。
- 當然,可能會調查重置每個人的密碼,這可能會提示公司正在發生攻擊,但它仍然可能提供足夠的時間來注入後門使用者,或者可能獲得一些輔助資訊以供以後使用/利用。
- 即使這種情況主要是學術性的(即在現實世界中不太可能發生),它仍然不是不可能的。
有關 SQL 注入的更多詳細資訊(涵蓋各種 RDBMS 和場景),請參閱Open Web Application Security Project (OWASP) 中的以下內容:
關於 SQL 注入和 SQL 截斷的相關 Stack Overflow 答案: