単一引用符をエスケープし、ユーザー入力を単一引用符で囲むことにより、SQLインジェクションから保護できますか?
-
02-07-2019 - |
質問
ユーザー入力を含むクエリを作成する場合、パラメーター化されたSQLクエリがユーザー入力をサニタイズする最適な方法であることがわかりますが、ユーザー入力を取得し、単一引用符をエスケープし、文字列全体を単一引用符で囲むことの何が問題なのでしょうか? 。コードは次のとおりです。
sSanitizedInput = "'" & Replace(sInput, "'", "''") & "'"
ユーザーが入力する単一引用符は二重単一引用符に置き換えられます。これにより、ユーザーが文字列を終了できなくなります。したがって、セミコロン、パーセント記号など、ユーザーが入力できるものはすべて実際にはコマンドの一部として実行されない文字列。 Microsoft SQL Server 2000を使用しているため、単一引用符が唯一の文字列区切り文字であり、文字列区切り文字をエスケープする唯一の方法であると考えているため、ユーザーが入力したものを実行する方法はありません。
これに対してSQLインジェクション攻撃を仕掛ける方法はありませんが、もしこれが私のように防弾であれば、他の誰かがすでにそれを考えていて、それが一般的な慣習であることを認識しています。私の質問はこれです:このコードの何が問題になっていますか?このサニタイズ技術を超えてSQLインジェクション攻撃を受ける方法を知っている人はいますか?この手法を活用するサンプルユーザー入力は非常に役立ちます。
更新:
ご回答ありがとうございました。私の研究で出会ったほとんどすべての情報は、このページのどこかに表示されました。これは、忙しい日々を過ごしてこの質問を手伝ってくれた人々の知性とスキルの証です。
回答をまだ受け入れていない理由は、このコードに対してSQLインジェクション攻撃を効果的に開始する方法がまだわからないためです。少数の人々は、バックスラッシュが1つの一重引用符をエスケープし、他を残して文字列を終了すると、残りの文字列がSQLコマンドの一部として実行されることを提案し、このメソッドがSQLを注入するのに役立つことを理解していますmySQLデータベース。ただし、MS SQL 2000では、単一引用符をエスケープする唯一の方法(見つけることができた)は、別の単一引用符を使用することです。バックスラッシュはそれを行いません。また、単一引用符のエスケープを停止する方法がない限り、残りのユーザー入力はすべて1つの連続した文字列として解釈されるため、実行されません。
入力をサニタイズするより良い方法があることは理解していますが、上で提供した方法が機能しない理由を学ぶことに本当に興味があります。このサニタイズ方法に対してSQLインジェクション攻撃を仕掛ける特定の方法を知っている人がいれば、ぜひ見たいです。
解決
まず第一に、それはただの悪い習慣です。入力の検証は常に必要ですが、それは常にiffyです。
さらに悪いことに、ブラックリストの検証には常に問題があり、受け入れる値/形式を明示的かつ厳密に定義する方がはるかに優れています。確かに、これは常に可能というわけではありませんが、ある程度までは常に行わなければなりません。
このテーマに関するいくつかの研究論文:
- http://www.imperva.com/docs/WP_SQL_Injection_Protection_LK.pdf
- http://www.it-docs.net/ddata/4954.pdf (開示、この最後のものは私のものでした;))
- https://www.owasp.org/images/d/d4/ OWASP_IL_2007_SQL_Smuggling.pdf (以前の論文に基づいていますが、現在は利用できません)
ポイントは、あなたが行うブラックリスト(および許容度が高すぎるホワイトリスト)はすべてバイパスできるということです。私の論文への最後のリンクは、引用のエスケープさえもバイパスできる状況を示しています。
これらの状況があなたに当てはまらないとしても、それはまだ悪い考えです。さらに、アプリが非常に小さい場合を除き、メンテナンスと、おそらくある程度のガバナンスに対処する必要があります。どのようにして、いつでもどこでも正常に実行されるようにするにはどうすればよいですか
適切な方法:
- ホワイトリストの検証:タイプ、長さ、形式、または許容値
- ブラックリストに登録する場合は、すぐに進みます。引用符のエスケープは適切ですが、他の緩和策のコンテキスト内です。
- コマンドおよびパラメーターオブジェクトを使用して、事前解析および検証
- パラメータ化されたクエリのみを呼び出します。
- さらに、ストアドプロシージャのみを使用してください。
- 動的SQLの使用を避け、クエリの構築に文字列連結を使用しないでください。
- SPを使用する場合、データベース内の権限を、必要なSPのみを実行するように制限し、テーブルに直接アクセスすることもできません。
- コードベース全体がSPを介してのみDBにアクセスすることも簡単に確認できます...
他のヒント
さて、この回答は質問の更新に関するものです:
"このサニタイズ方法に対してSQLインジェクション攻撃を仕掛ける具体的な方法を知っている人がいるなら、ぜひ見たいです。
今、MySQLバックスラッシュのエスケープに加えて、実際にMSSQLについて話していることを考慮すると、SQLがコードをインジェクトする3つの可能な方法が実際にあります
sSanitizedInput =" '" & Replace(sInput、" '&quot ;,"' '")& " '"
これらすべてが常に有効とは限らず、実際のコードに大きく依存していることを考慮してください:
- 2次SQLインジェクション-エスケープ後にデータベースから取得したデータに基づいてSQLクエリを再構築する場合、エスケープすると、データはエスケープせずに連結され、間接的にSQLインジェクションされる場合があります。 をご覧ください
- 文字列の切り捨て-(もう少し複雑)-シナリオは、ユーザー名とパスワードの2つのフィールドがあり、SQLが両方を連結する場合です。そして、両方のフィールド(または最初のフィールド)の長さには厳しい制限があります。たとえば、ユーザー名は20文字に制限されています。このコードがあるとします:
username = left(Replace(sInput, "'", "''"), 20)
次に、取得するものはユーザー名であり、エスケープされてから20文字にトリミングされます。ここでの問題-私は20文字(たとえば、19の文字の後)に引用を貼り付け、あなたのエスケープ引用は(21文字目に)トリミングされます。次に、SQL
sSQL = "select * from USERS where username = '" + username + "' and password = '" + password + "'"
前述の不正なユーザー名と組み合わせると、パスワードは既に引用符の外側になり、ペイロードのみが直接含まれます。
3. Unicodeスマグリング-特定の状況では、引用符のように見えるが、そうではない高レベルのユニコード文字を渡すことができます。データベース、突然 です。検証するときは引用ではないので、簡単に進みます。詳細については、以前の回答を参照してください。元の研究へのリンクです。
簡単に言うと、クエリを自分でエスケープしないでください。あなたは間違いを犯すにちがいありません。代わりに、パラメータ化されたクエリを使用するか、何らかの理由でそれができない場合は、これを行う既存のライブラリを使用してください。自分でやる理由はありません。
質問が出されてからかなり時間が経ったが、..
「引数を引用する」手順で攻撃を開始する1つの方法は、文字列の切り捨てです。 MSDNによると、SQL Server 2000 SP4(およびSQL Server 2005 SP1)では、長すぎる文字列は静かに切り捨てられます。
文字列を引用すると、文字列のサイズが大きくなります。すべてのアポストロフィが繰り返されます。 これを使用して、SQLの一部をバッファの外側にプッシュできます。したがって、where句の一部を効果的に削除できます。
これは、おそらく、「update」ステートメントを悪用して想定されるすべてのチェックを行わない「ユーザー管理」ページのシナリオで主に役立つでしょう。
したがって、すべての引数を引用する場合は、文字列のサイズがどうなるかを確認し、切り捨てられないことを確認してください。
パラメータを使用することをお勧めします。常に。データベースでそれを強制できるといいのですが。また、副作用として、より多くのステートメントが同じように見えるため、キャッシュヒットが改善される可能性が高くなります。 (これはOracle 8でも確かに当てはまりました)
入力サニテーションは、半ばお望みのものではありません。お尻全体を使用します。テキストフィールドで正規表現を使用します。数値を適切な数値型にTryCastし、機能しない場合は検証エラーを報告します。入力中の攻撃パターン(「-」など)を検索するのは非常に簡単です。ユーザーからのすべての入力が敵対的であると想定します。
「高度な検索」機能を扱うときにこのテクニックを使用しました。クエリを最初から作成することが唯一の実行可能な答えでした。 (例:製品属性の無制限のセットに基づいてユーザーが製品を検索できるようにし、列とその許容値をGUIコントロールとして表示して、ユーザーの学習しきい値を減らします。)
それ自体は安全です。ただし、別の回答者が指摘したように、バックスペースのエスケープにも対処する必要がある場合があります(ただし、少なくともADOまたはADO.NETを使用してクエリをSQL Serverに渡す場合は、すべてのデータベースまたはテクノロジを保証できません)。
問題は、どの文字列にユーザー入力が含まれているか(常に悪意のある可能性がある)、およびどの文字列が有効なSQLクエリであるかを確実に確認する必要があることです。トラップの1つは、データベースの値を使用する場合です。これらの値は元々ユーザー指定のものでしたか?その場合、それらもエスケープする必要があります。私の答えは、SQLクエリを構築する際に、できるだけ遅く(ただし、後ほど!)サニタイズすることです。
ただし、ほとんどの場合、パラメーターバインドが最も効果的です。
とにかくあなたが知っているように、それは悪い考えです。
次のような文字列で引用符をエスケープするようなものはどうですか:\ '
あなたの置換は次のようになります:\ ''
バックスラッシュが最初の引用符をエスケープする場合、2番目の引用符は文字列を終了します。
簡単な答え:時々動作しますが、常に動作するわけではありません。 あなたが行うすべてでホワイトリスト検証を使用したいが、それが常に可能であるとは限らないことを理解しているため、最良の推測ブラックリストを使用せざるを得ない。同様に、パラメーター化されたストアドプロシージャをすべてで使用したい場合もありますが、それが常に可能であるとは限らないため、パラメーターを指定してsp_executeを使用する必要があります。
使用可能なブラックリストを回避する方法はいくつかあります(ホワイトリストもあります)。
まともな記事はこちら: http://www.owasp.org/index。 php / Top_10_2007-A2
これをクイックフィックスとして実行する必要がある場合は、実際のリリースを実行する時間を与えてください。しかし、あなたが安全だとは思わないでください。
SQLインジェクションから安全にするために、例外なく2つの方法があります。準備されたステートメントまたはパラメータ化されたストアドプロシージャ。
パラメーター化されたクエリを使用できる場合は、常に使用する必要があります。必要なのは、1つのクエリがネットをすり抜け、DBが危険にさらされることだけです。
はい、誰かが SET QUOTED_IDENTIFIER OFF そして二重引用符を使用します。
編集:悪意のあるユーザーが引用識別子をオフにすることを許可しないほど簡単ではありません:
SQL Server Native Client ODBCドライバーとSQL Server用SQL Server Native Client OLE DBプロバイダーは、接続時にQUOTED_IDENTIFIERを自動的にONに設定します。これは、ODBCデータソース、ODBC接続属性、またはOLE DB接続プロパティで構成できます。 DB-Libraryアプリケーションからの接続の場合、SET QUOTED_IDENTIFIERのデフォルトはOFFです。
ストアドプロシージャが作成されると、 SET QUOTED_IDENTIFIERおよびSET ANSI_NULLS設定がキャプチャされ、そのストアドプロシージャの後続の呼び出しに使用されます。
SET QUOTED_IDENTIFIERは、 ALTER DATABASEのQUOTED_IDENTIFER設定にも対応しています。
SET QUOTED_IDENTIFIERは、解析時に設定されます。解析時に設定すると、バッチまたはストアドプロシージャにSETステートメントが存在する場合、コード実行が実際にそのポイントに到達したかどうかに関係なく、それが有効になります。 SETステートメントは、ステートメントが実行される前に有効になります。
必ずしも知らないうちにQUOTED_IDENTIFIERがオフになる方法はたくさんあります。確かに、これはあなたが探している喫煙銃の悪用ではありませんが、かなり大きな攻撃対象領域です。もちろん、二重引用符もエスケープした場合は、始めたところに戻ります。 ;)
次の場合、防御は失敗します。
- クエリは文字列ではなく数字を予期しています
- 単一引用符を表す他の方法がありました。
- \ 039などのエスケープシーケンス
- ユニコード文字
(後者の場合、置き換えを行った後にのみ展開されるものでなければなりません)
パトリック、数値入力であっても、すべての入力を単一引用符で囲んでいますか?数値を入力しているが、その引用符を一重引用符で囲まない場合、露出があります。
ユーザー入力のサニタイズがすべてであることは、何といコードでしょうか!次に、SQLステートメントの不格好なStringBuilder。プリペアドステートメントメソッドを使用すると、コードがはるかにクリーンになり、SQLインジェクションの利点は非常に優れた追加です。
また、なぜ車輪を再発明するのですか?
一重引用符を(一見)2つの一重引用符に変更するのではなく、単にアポストロフィ、引用符に変更する、または完全に削除しないのはなぜですか?
いずれにせよ、それはちょっと面倒です...特に、単一引用符を使用する可能性のあるもの(名前など)を合法的に持っている場合は...
注:また、このメソッドは、アプリで作業しているすべての人が入力をデータベースにヒットする前に必ずサニタイズすることを覚えていることを前提としています。これはほとんどの場合現実的ではありません。
文字列に有効な解決策が見つかるかもしれませんが、数値述語の場合は、数値のみを渡すことも確認する必要があります(単純なチェックは、int / double / decimalとして解析できますか?)。
多くの追加作業が必要です。
それはうまくいくかもしれませんが、私にとってはちょっとした愚劣のようです。代わりに正規表現に対してテストして、各文字列が有効であることを検証することをお勧めします。
はい、できます、...
トピックを学習した後、あなたが提案したようにサニタイズされた入力は安全であると思うが、これらのルールの下でのみ:
-
ユーザーからの文字列値が文字列リテラル以外のものになることを許可しません(つまり、構成オプション:"追加のSQL列名/式をここに入力しないでください:")。文字列以外の値型(数値、日付など):それらをネイティブデータ型に変換し、各データ型からのSQLリテラルのルーチンを提供します。
- SQLステートメントは検証に問題があります
-
nvarchar
/nchar
列(およびN
で文字列リテラルにプレフィックスを付ける)を使用するか、に入る値を制限するvarchar
/char
カラムのみをASCII文字に変換(例:SQLステートメントの作成時に例外をスロー)- こうすることで、CHAR(700)からCHAR(39)への自動アポストロフィ変換を回避できます(そして、おそらく他の同様のUnicodeハック)
-
常に実際の列の長さに合うように値の長さを検証します(長い場合は例外をスローします)
- 切り捨て時にスローされたSQLエラーをバイパスすることができるSQL Serverの既知の欠陥がありました(サイレント切り捨てにつながります)
-
SET QUOTED_IDENTIFIER
が常にON
- 注意してください、解析時間、つまりアクセスできないコードのセクションでも有効になります
これら4つの点を順守すると、安全になります。それらのいずれかに違反すると、SQLインジェクションの方法が開きます。