web-dev-qa-db-ja.com

ストアドプロシージャ内のこのクエリで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で実行され、結果がエラーではなく正しく表示されていることを示しています。

enter image description here

ところで、クエリの実行が完了した後、セミコロンの後にその部分を追加しました。その後、もう一度実行しましたが、テーブルtblActorが存在するかどうかを確認したところ、まだそこにありました。私は何か間違ったことをしていますか?それともこれは本当に注射耐性があるのですか?私がここで尋ねようとしているのは、このようなストアドプロシージャは安全ですか?ありがとうございました。

18
Ravi

次の理由により、このコードは正しく機能します。

  1. パラメータ化、および
  2. 動的SQLを実行していない

SQLインジェクションが機能するためには、クエリ文字列(これは実行していません)を構築し、not単一のアポストロフィ(_'_)をエスケープされたアポストロフィに変換する必要があります(_''_)(これらは入力パラメーターを介してエスケープされます)。

「妥協した」値を渡そうとすると、_'Male; DROP TABLE tblActor'_文字列は単なる文字列です。

さて、もしあなたが次の線に沿って何かをしていたら:

_DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'SELECT fields FROM table WHERE field23 = '
          + @InputParam;

EXEC(@SQL);
_

次に、thatはSQLインジェクションの影響を受けます。これは、thatクエリが現在の事前解析されたコンテキストにないためです。そのクエリは現時点では単なる別の文字列です。したがって、_@InputParam_の値は_'2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;_になる可能性があり、そのクエリは次のようにレンダリングおよび実行されるため、問題が発生する可能性があります。

_SELECT fields FROM table WHERE field23 = '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
_

これは、ストアドプロシージャを使用する(いくつかの)主な理由の1つです:本質的により安全です(使用するパラメーターの値を検証せずに上記で示したようなクエリを作成してセキュリティを回避しない限り)。動的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;
_

このアプローチを使用すると、DATETIME入力パラメーターに_'2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;_を渡そうとすると、ストアドプロシージャの実行時にエラーが発生します。または、ストアドプロシージャが_@InputParameter_をNVARCHAR(100)として受け入れたとしても、その_sp_executesql_呼び出しに渡すには、DATETIMEに変換する必要があります。また、ダイナミックSQLのパラメーターが文字列型であっても、最初にストアドプロシージャに入ると、単一のアポストロフィは自動的に二重のアポストロフィにエスケープされます。

あまり知られていない種類の攻撃があり、攻撃者は入力フィールドをアポストロフィで埋めようとします。これにより、動的SQLの構築に使用されるが、宣言された文字列が小さすぎるとすべてに適合できないそして、最後のアポストロフィを押し出し、どういうわけか正しい数のアポストロフィで終了して、文字列内で「エスケープ」されないようにします。これはSQL Truncationと呼ばれ、MSDNマガジンの記事「新しいSQL Truncation攻撃とそれらを回避する方法」でBala Neerumallaによって話されましたが、この記事はオンラインではありません。この記事を含む問題— MSDNマガジンの2006年11月版 —は、。chm フォーマット)。ダウンロードした場合、デフォルトのセキュリティ設定により開かない場合があります。これが発生した場合は、MSDNMagazineNovember2006en-us.chmファイルを右クリックして、[プロパティ]を選択します。これらのタブの1つに、「このタイプのファイルを信頼する」(またはそのようなもの)オプションがあり、チェック/有効化する必要があります。 [OK]ボタンをクリックして、。chmファイルをもう一度開いてみてください。

切り捨て攻撃の別のバリエーションは、ローカル変数を使用して、エスケープするために二重引用符を二重にした「安全な」ユーザー指定の値を格納することを想定し、そのローカル変数を埋めて単一引用符を配置することです。最後に。ここでの考え方は、ローカル変数のサイズが適切でない場合、2番目の単一引用符の末尾に十分なスペースがないため、変数を単一の単一引用符で終了し、それを次の単一引用符と組み合わせるということです。ダイナミック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--';
_

これを修正するのは簡単です。次のいずれかを実行してください。

  1. 動的SQLを使用しないでください[〜#〜]絶対に[〜#〜]必要です! (最初に検討する必要があるため、これを最初にリストします)。
  2. ローカル変数のサイズを適切に設定します(つまり、渡されるすべての文字が単一引用符である場合に備えて、入力パラメーターのサイズの2倍にする必要があります)。
  3. ローカル変数を使用して「固定」値を格納しないでください。動的SQLの作成にREPLACE()を直接入れるだけです。

    _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--';
    _

上記の切り捨ての例に関するメモ:

  1. はい、これは非常に不自然な例です。注入する15文字だけでできることは多くありません。もちろん、_DELETE tableName_は破壊的ですが、バックドアユーザーを追加したり、管理者パスワードを変更したりする可能性は低くなります。
  2. このタイプの攻撃おそらくは、コード、テーブル名などの知識が必要です。ランダムな見知らぬ人/スクリプトキディによって行われる可能性は低いですが、私は他の誰も知らない特定のWebページの脆弱性を知っていて、気が動転した元従業員によって攻撃されました。つまり、攻撃者はシステムをよく理解していることがあります。
  3. 確かに、すべての人のパスワードをリセットすることは調査される可能性が高く、攻撃が発生していることを会社に知らせてしまう可能性がありますが、それでもバックドアユーザーを挿入するのに十分な時間を提供する可能性がありますまたは、後で使用/悪用するためのいくつかの二次情報を取得します。
  4. このシナリオがほとんど学術的なものであっても(つまり、現実の世界では起こりそうにありません)、それでも不可能ではありません。

SQLインジェクションに関連する詳細情報(さまざまなRDBMSとシナリオをカバー)については、 Open Web Application Security Project (OWASP)から以下を参照してください。
SQLインジェクションのテスト

SQLインジェクションとSQLトランケーションに関する関連スタックオーバーフローの回答:
'エスケープ文字を置き換えた後のT-SQLの安全性は?

38
Solomon Rutzky

簡単なことは、データをコマンドと混同しないことです。パラメータの値がコマンドの一部として扱われることはなく、したがって実行されることもありません。

私はこれについてブログに書いた: http://blogs.lobsterpot.com.au/2015/02/10/sql-injection-the-golden-rule/

2
Rob Farley