您應該檢閱所有呼叫 EXECUTE、EXEC 或 sp_executesql 的程式碼。您可以使用類似以下的查詢,來幫助您識別包含這些陳述式的程序。這個查詢會檢查 EXECUTE 或 EXEC 這些字後面的 1、2、3 或 4 個空格。
SELECT object_Name(id) FROM syscomments
WHERE UPPER(text) LIKE '%EXECUTE
(%'
OR UPPER(text) LIKE '%EXECUTE (%'
OR UPPER(text) LIKE '%EXECUTE (%'
OR UPPER(text) LIKE '%EXECUTE (%'
OR UPPER(text) LIKE '%EXEC (%'
OR UPPER(text) LIKE '%EXEC (%'
OR UPPER(text) LIKE '%EXEC (%'
OR UPPER(text) LIKE '%EXEC (%'
OR UPPER(text) LIKE '%SP_EXECUTESQL%'
用 QUOTENAME() 和 REPLACE() 包裝參數
在每一個選取的預存程序中,確認動態 Transact-SQL 使用的所有變數都已正確處理。來自預存程序的輸入參數或從資料表中讀取的資料應該用 QUOTENAME() 或 REPLACE() 包裝。請記住,傳遞到 QUOTENAME() 之 @variable 的值是 sysname 的值,其最大長度為 128 個字元。
|
@variable
|
建議的包裝函式
|
|---|
|
安全性實體的名稱
|
QUOTENAME(@variable)
|
|
小於等於 128 個字元的字串
|
QUOTENAME(@variable, '''')
|
|
小於 128 個字元的字串
|
REPLACE(@variable,'''', '''''')
|
當您使用這項技術時,SET 陳述式可修改如下:
--Before:
SET @temp = N'select * from authors where au_lname='''
+ @au_lname + N''''
--After:
SET @temp = N'select * from authors where au_lname='''
+ REPLACE(@au_lname,'''','''''') + N''''
資料截斷啟用資料隱碼
任何指派給變數的動態 Transact-SQL 若大於該變數已配置的緩衝區,就會被截斷。能夠非預期傳遞長字串給預存程序來強制執行陳述式截斷的攻擊者,就可以操作此結果。例如,由下列指令碼建立的預存程序很容易因為截斷而啟用資料隱碼。
CREATE PROCEDURE sp_MySetPassword
@loginname sysname,
@old sysname,
@new sysname
AS
-- Declare variable.
-- Note that the buffer here is only 200 characters long.
DECLARE @command varchar(200)
-- Construct the dynamic Transact-SQL.
-- In the following statement, we need a total of 154 characters
-- to set the password of 'sa'.
-- 26 for UPDATE statement, 16 for WHERE clause, 4 for 'sa', and 2 for
-- quotation marks surrounded by QUOTENAME(@loginname):
-- 200 – 26 – 16 – 4 – 2 = 154.
-- But because @new is declared as a sysname, this variable can only hold
-- 128 characters.
-- We can overcome this by passing some single quotation marks in @new.
SET @command= 'update Users set password=' + QUOTENAME(@new, '''') + ' where username=' + QUOTENAME(@loginname, '''') + ' AND password = ' + QUOTENAME(@old, '''')
-- Execute the command.
EXEC (@command)
GO
將 154 個字元傳遞至 128 個字元的緩衝區之後,攻擊者可以在不知道舊密碼的情況下,設定 sa 的新密碼。
EXEC sp_MySetPassword 'sa', 'dummy', '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012'''''''''''''''''''''''''''''''''''''''''''''''''''
基於這個原因,您應該對命令變數使用大型緩衝區,或直接在 EXECUTE 陳述式內執行動態 Transact-SQL。
使用 QUOTENAME(@variable, '''') 和 REPLACE() 時會截斷
如果超過已配置的空間,QUOTENAME() 和 REPLACE() 傳回的字串會自動被截斷。下列範例所建立的預存程序會顯示可能發生的情況。
CREATE PROCEDURE sp_MySetPassword
@loginname sysname,
@old sysname,
@new sysname
AS
-- Declare variables.
DECLARE @login sysname
DECLARE @newpassword sysname
DECLARE @oldpassword sysname
DECLARE @command varchar(2000)
-- In the following statements, the data stored in temp variables
-- will be truncated because the buffer size of @login, @oldpassword,
-- and @newpassword is only 128 characters, but QUOTENAME() can return
-- up to 258 characters.
SET @login = QUOTENAME(@loginname, '''')
SET @oldpassword = QUOTENAME(@old, '''')
SET @newpassword = QUOTENAME(@new, '''')
-- Construct the dynamic Transact-SQL.
-- If @new contains 128 characters, then @newpassword will be '123... n
-- where n is the 127th character.
-- Because the string returned by QUOTENAME() will be truncated,
-- it can be made to look like the following statement:
-- UPDATE Users SET password ='1234. . .[127] WHERE username=' -- other stuff here
SET @command = 'UPDATE Users set password = ' + @newpassword
+ ' where username =' + @login + ' AND password = ' + @oldpassword;
-- Execute the command.
EXEC (@command)
GO
因此,下列陳述式將所有使用者的密碼設為先前程式碼傳遞的值。
EXEC sp_MyProc '--', 'dummy', '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678'
您可以在使用 REPLACE() 時超出已配置的緩衝區來強制執行字串截斷。下列範例所建立的預存程序會顯示可能發生的情況。
CREATE PROCEDURE sp_MySetPassword
@loginname sysname,
@old sysname,
@new sysname
AS
-- Declare variables.
DECLARE @login sysname
DECLARE @newpassword sysname
DECLARE @oldpassword sysname
DECLARE @command varchar(2000)
-- In the following statements, data will be truncated because
-- the buffers allocated for @login, @oldpassword and @newpassword
-- can hold only 128 characters, but QUOTENAME() can return
-- up to 258 characters.
SET @login = REPLACE(@loginname, '''', '''''')
SET @oldpassword = REPLACE(@old, '''', '''''')
SET @newpassword = REPLACE(@new, '''', '''''')
-- Construct the dynamic Transact-SQL.
-- If @new contains 128 characters, @newpassword will be '123...n
-- where n is the 127th character.
-- Because the string returned by QUOTENAME() will be truncated, it
-- can be made to look like the following statement:
-- UPDATE Users SET password='1234…[127] WHERE username=' -- other stuff here
SET @command= 'update Users set password = ''' + @newpassword + ''' where username='''
+ @login + ''' AND password = ''' + @oldpassword + '''';
-- Execute the command.
EXEC (@command)
GO
就像 QUOTENAME() 一樣,也可以藉由宣告大到適合所有情況的暫時變數來避免 REPLACE() 截斷字串。可能的話,您應該直接在動態 Transact-SQL 內呼叫 QUOTENAME() 或 REPLACE()。否則,您可以計算必要的緩衝區大小如下。對於 @outbuffer = QUOTENAME(@input),@outbuffer 的大小應該為 2*(len(@input)+1). 。當您使用 REPLACE() 和雙引號時 (如上例所示),大小為 2*len(@input) 的緩衝區已足夠。
下列計算可涵蓋所有情況:
While len(@find_string) > 0, required buffer size =
round(len(@input)/len(@find_string),0) * len(@new_string)
+ (len(@input) % len(@find_string))
使用 QUOTENAME(@variable, ']') 時會截斷
當 SQL Server 安全性實體的名稱傳遞至使用 QUOTENAME(@variable, ']') 格式的陳述式時,會發生截斷。以下範例說明這點。
CREATE PROCEDURE sp_MyProc
@schemaname sysname,
@tablename sysname,
AS
-- Declare a variable as sysname. The variable will be 128 characters.
-- But @objectname actually must allow for 2*258+1 characters.
DECLARE @objectname sysname
SET @objectname = QUOTENAME(@schemaname)+'.'+ QUOTENAME(@tablename)
-- Do some operations.
GO
當您串連 sysname 類型的值時,應使用夠大的暫時變數來保留每個值最多 128 個字元。可能的話,請直接在動態 Transact-SQL 內呼叫 QUOTENAME()。否則,您可以計算必要的緩衝區大小,如上一節所述。