検証は、EXECUTE、EXEC、または sp_executesql を呼び出すすべてのコードに対して行う必要があります。次のようなクエリを使用すると、これらのステートメントを含むプロシージャの識別に役立てることができます。このクエリは、EXECUTE または EXEC という語の後に 1 ~ 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
攻撃者は、sa の古いパスワードがわからなくても、128 文字バッファに 154 文字を渡して新しいパスワードを設定できます。
EXEC sp_MySetPassword 'sa', 'dummy', '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012'''''''''''''''''''''''''''''''''''''''''''''''''''
このため、コマンド変数には大きなバッファを使用するか、Transact-SQL は直接 EXECUTE ステートメント内で動的に実行する必要があります。
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() を直接呼び出します。あるいは、前述のように必要なバッファ サイズを計算できます。