web-dev-qa-db-ja.com

SQLインジェクション防止メカニズムがパラメーター化されたクエリを使用する方向に進化したのはなぜですか?

私の見たところ、SQLインジェクション攻撃は次の方法で防ぐことができます。

  1. 入力の慎重なスクリーニング、フィルタリング、エンコード(SQLへの挿入前)
  2. 準備されたステートメント /パラメータ化されたクエリの使用

それぞれに長所と短所があると思いますが、なぜ#2が離陸し、インジェクション攻撃を防ぐための事実上の方法であると考えられたのですか?安全でエラーが発生しにくいのですか、それとも他の要因があったのですか?

私が理解しているように、#1が適切に使用され、すべての警告が処理されれば、#2と同じくらい効果的です。

サニタイジング、フィルタリング、およびエンコーディング

sanitizingfiltering、およびencodingの意味の間に、私の側でいくつかの混乱がありました。私の目的のために、上記のすべてをオプション1と見なすことができると言います。この場合、サニタイズとフィルタリングがmodify or discard入力データである可能性があることを理解していますエンコード中に- データをそのまま保存ですが、インジェクション攻撃を回避するために適切にエンコードします。データのエスケープは、それをエンコードする方法の1つと見なすことができると思います。

パラメータ化されたクエリとエンコーディングライブラリ

parameterized queriesencoding librariesの概念が互換的に扱われる答えがあります。私が間違っている場合は訂正してください。しかし、それらは異なるものであるという印象を受けています。

私が理解していることは、SQL [プログラム]を変更するためのencoding librariesは、SQL [プログラム]を変更するために常にpotentialであるということです。送信される前にSQL自体に変更を加えているためです。 RDBMS。

一方、Parameterized queriesは、SQLプログラムをRDBMSに送信し、RDBMSがクエリを最適化し、クエリ実行プランを定義し、使用するインデックスを選択するなどして、データをRDBMS自体の最後のステップ。

エンコーディングライブラリ

  data -> (encoding library)
                  |
                  v
SQL -> (SQL + encoded data) -> RDBMS (execution plan defined) -> execute statement

パラメータ化されたクエリ

                                               data
                                                 |
                                                 v
SQL -> RDBMS (query execution plan defined) -> data -> execute statement

歴史的意義

一部の回答では、歴史的に、パラメータ化されたクエリ(PQ)はパフォーマンス上の理由から作成され、エンコーディングの問題を対象としたインジェクション攻撃が一般的になる前に作成されました。ある時点で、PQがインジェクション攻撃に対してもかなり効果的であることが明らかになりました。私の質問の精神を守るために、SQLインジェクション攻撃を防ぐことに関して、なぜPQが選択された方法であり続け、なぜ他のほとんどの方法より優れているのですか?

59
Dennis

問題は、#1では、対象となるSQLバリアント全体を効果的に解析および解釈して、それがすべきでないことを行っているかどうかを知る必要があることです。データベースを更新するときは、コードを最新の状態に保ちます。 Everywhereクエリの入力を受け入れます。 Andそれを台無しにしない。

そう、そういうものはSQLインジェクション攻撃を阻止するでしょうが、それを実装するのはとてつもなく高価です。

147
Telastyn

オプション1は解決策ではないためです。スクリーニングとフィルタリングは、無効な入力を拒否または削除することを意味します。ただし、入力は有効な場合があります。たとえば、アポストロフィは "O'Malley"という名前の有効な文字です。 SQLで使用する前に正しくエンコードする必要があります。これは、準備済みステートメントが行うことです。


メモを追加した後、基本的に、機能的に類似した独自のコードを最初から作成するのではなく、標準ライブラリ関数を使用する理由を尋ねているようです。 alwaysは、独自のコードを作成するよりも標準ライブラリソリューションを優先する必要があります。作業が減り、保守性が向上します。これはany機能の場合に当てはまりますが、特にセキュリティ上重要なものについては、自分でホイールを再発明することはまったく意味がありません。

80
JacquesB

文字列処理を実行しようとしている場合、SQLクエリを実際に生成しているわけではありません。 SQLクエリを生成できる文字列を生成しています。エラーとバグのためにlotの余地を開く間接レベルがあります。ほとんどの状況で、プログラムで何かとやり取りできることを考えると、これは本当に意外なことです。たとえば、リスト構造があり、アイテムを追加する場合、通常は行いません。

List<Integer> list = /* a list of 1, 2, 3 */
String strList = list.toString();   /* to get "[1, 2, 3]" */
strList = /* manipulate strList to become "[1, 2, 5, 3]" */
list = parseList(strList);

誰かがそうすることを提案した場合、あなたはそれがかなりばかげていると正しく答えるでしょう、そしてそれはただやるべきです:

List<Integer> list = /* ... */;
list.add(5, position=2);

これは、概念レベルでデータ構造と相互作用します。それは、その構造がどのように印刷または解析されるかに依存しません。これらは完全に直交する決定です。

最初のアプローチは最初のサンプルのようなものです(少し悪いだけです):が、必要なクエリとして正しく解析される文字列をプログラムで構築できると想定しています。それはパーサーと文字列処理ロジック全体に依存します。

準備されたクエリを使用する2番目のアプローチは、2番目のサンプルにかなり似ています。準備されたクエリを使用する場合、基本的には正当であるがその中にいくつかのプレースホルダーがある疑似クエリを解析し、APIを使用してそこにある値を正しく置き換えます。解析プロセスが不要になり、文字列処理について心配する必要はありません。

一般に、概念的なレベルで物事を操作する方がはるかに簡単で、エラーが発生しにくくなります。クエリは文字列ではありません。クエリは、文字列を解析したり、プログラムで(または他の方法で作成したり)作成したときに得られるものです。

単純なテキスト置換を行うCスタイルのマクロと、任意のコード生成を行うLISPスタイルのマクロには、よく似たところがあります。 Cスタイルのマクロを使用すると、ソースコードのテキストを置き換えることができます。つまり、構文エラーや誤解を招くような動作を引き起こす可能性があります。 LISPマクロを使用すると、コンパイラが処理する形式でコードを生成します(つまり、コンパイラが処理する前にリーダーが処理する必要があるテキストではなく、コンパイラが処理する実際のデータ構造を返します)。 。ただし、LISPマクロを使用すると、解析エラーになるようなものを生成することはできません。たとえば、(let((a b)aを生成することはできません。

ただし、LISPマクロを使用しても、そこにあるはずの構造を必ずしも認識しているわけではないため、不正なコードが生成される可能性があります。たとえば、LISPでは、(let((ab))a)は、「変数aの変数bの値への新しい字句バインディングを確立してから、aの値を返す」、および- (let(ab)a)は、「変数aとbの新しい字句バインディングを確立し、両方をnilに初期化してから、aの値を返す」ことを意味します。どちらも構文的には正しいですが、意味が異なります。この問題を回避するには、セマンティックに対応した関数を使用して、次のようにします。

Variable a = new Variable("a");
Variable b = new Variable("b");
Let let = new Let();
let.getBindings().add(new LetBinding(a,b));
let.setBody(a);
return let;

そのようなものでは、構文的に無効なものを返すのは不可能であり、はるかに難しい誤って意図したものと異なるものを返す。

60
Joshua Taylor

データベースはクエリのパラメーター化されていないバージョンをキャッシュできるため、オプション#2は一般的にベストプラクティスと見なされます。パラメータ化されたクエリは、SQLインジェクションの問題よりも数年前に(私は信じています)、2羽の鳥を1つの石で殺すことができるのは偶然です。

21
JasonB

単純に言った:彼らはしなかった。あなたの声明:

SQLインジェクション防止メカニズムがパラメーター化されたクエリを使用する方向に進化したのはなぜですか?

根本的に欠陥があります。パラメータ化されたクエリは、SQLインジェクションが少なくとも広く知られているよりもずっと長く存在してきました。これらは一般に、LOB(基幹業務)アプリケーションが持つ通常の「検索用フォーム」機能での文字列の集中を回避する方法として開発されました。多くの-多くの-年後、誰かが上記の文字列操作でセキュリティの問題を発見しました。

25年前にSQLを実行したことを覚えています(インターネットが広く使用されていなかったとき-まだ始まったばかりでした)、SQLとIBM DB5 IIRCバージョン5を比較したことを覚えています。

20
TomTom

他のすべての良い答えに加えて:

#2の方が優れている理由は、コードからデータを分離するためです。 #1では、データはコードの一部であり、そこからすべての悪いことが起こります。 #1ではクエリを取得し、クエリがデータをデータとして理解することを確認するために追加の手順を実行する必要がありますが、#2ではコードとコードを取得し、データはデータです。

13
Pieter B

SQLインジェクション防御を提供することとは別に、パラメーター化されたクエリは、多くの場合、1回だけコンパイルされ、その後、異なるパラメーターで複数回実行されるという追加の利点があります。

SQLデータベースの観点からselect * from employees where last_name = 'Smith'およびselect * from employees where last_name = 'Fisher'は明らかに異なるため、個別の解析、コンパイル、および最適化が必要です。また、コンパイルされたステートメントを格納するための専用のメモリ領域の個別のスロットも使用します。負荷の高いシステムで、パラメータの異なる類似のクエリが多数ある場合、計算とメモリのオーバーヘッドが大きくなる可能性があります。

その後、パラメーター化されたクエリを使用すると、パフォーマンスが大幅に向上することがよくあります。

11
mustaccio

待って、でもなぜ?

オプション1は、あらゆるタイプの入力に対してサニタイズルーチンを作成する必要があることを意味しますが、オプション2は、エラーが発生しにくく、コードを記述/テスト/維持するためのコードが少なくなります。

ほぼ間違いなく"すべての注意事項に注意"は、あなたが考えているよりも複雑になる可能性があり、言語(たとえば、Java PreparedStatement)は、あなたは考える。

準備されたステートメントまたはパラメーター化されたクエリはデータベースサーバーでプリコンパイルされるため、パラメーターが設定されている場合、クエリはSQL文字列ではないため、SQL連結は行われません。追加の利点は、RDBMSがクエリをキャッシュし、後続の呼び出しはパラメーター値が異なる場合でも同じSQLであると見なされるのに対し、連結SQLではクエリが異なる値で実行されるたびにクエリが異なり、RDBMSがそれを解析する必要があることです。 、実行プランを再作成する、など。

5

理想的な「サニタイズ、フィルター、およびエンコード」アプローチがどのようになるかを想像してみましょう。

特定のアプリケーションのコンテキストでは、サニタイズとフィルタリングは理にかなっているかもしれませんが、最終的にはどちらも「このデータをデータベースに入れることはできません」ということになります。アプリケーションにとってはそれは良い考えかもしれませんが、データベースに任意の文字を格納できるようにする必要があるアプリケーションがあるため、これは一般的なソリューションとして推奨できるものではありません。

エンコードはそのままにします。まず、エスケープ文字を追加して文字列をエンコードする関数を用意し、エスケープ文字を自分で置き換えることができるようにします。異なるデータベースでは異なる文字のエスケープが必要なため(一部のデータベースでは、\''''の有効なエスケープシーケンスですが、他のデータベースでは使用できません)、この関数をデータベースで提供する必要がありますベンダー。

ただし、すべての変数が文字列であるとは限りません。整数または日付に置き換える必要がある場合があります。これらは文字列とは異なる方法で表現されるため、さまざまなエンコード方法が必要であり(ここでも、これらはデータベースベンダーに固有である必要があります)、さまざまな方法でクエリに代入する必要があります。

したがって、データベースがあなたのために置換を処理した場合、物事はより簡単になるでしょう-クエリが期待するタイプ、データを安全にエンコードする方法、そしてそれらをクエリに安全に置き換える方法をすでに知っているので、心配する必要はありませんあなたのコードでそれを。

この時点で、パラメーター化されたクエリを再発明しました。

クエリがパラメータ化されると、パフォーマンスの最適化や監視の簡素化など、新しい機会が開かれます。

エンコーディングは正しく行うのが難しく、エンコーディングを正しく行うことはパラメータ化と区別がつきません。

クエリを作成する方法として文字列補間が本当に必要な場合は、プラグイン可能な文字列補間が可能な言語がいくつかあります(ScalaとES2015が思い浮かびます)。 therearelibraries これにより、文字列補間のように見えるがSQLインジェクションからは安全なパラメーター化されたクエリを記述できます-ES2015構文では次のようになります。

import {sql} from 'cool-sql-library'

let result = sql`select *
    from users
    where user_id = ${user_id}
      and password_hash = ${password_hash}`.execute()

console.log(result)
1
James_pic

私はSQLを使用したことがありません。しかし、明らかにあなたは人々がどんな問題を抱えているかについて聞いており、SQL開発者はこの「SQLインジェクション」の問題に問題を抱えていました。長い間、それを理解することができませんでした。そして、ユーザーが入力した文字列を連結することにより、SQLステートメント、実際のテキストSQLソースステートメントを作成している人々がいることに気付きました。そして、その実現への私の最初の考えは衝撃でした。トータルショック。私はどう思いましたか:誰もがそんなにばかげて愚かで、そのようなプログラミング言語でステートメントを作成できるでしょうか? C、C++、Java、Swift開発者にとって、これはまったくの狂気です。

つまり、C文字列を引数として取り、同じ文字列を表すCソースコードの文字列リテラルとまったく同じように見える別の文字列を生成するC関数を記述することはそれほど難しくありません。たとえば、この関数は、abcを「abc」に、「abc」を「\」abc \」に、「\」abc\""を「\」\\ "abc \\"\""に変換します。 (まあ、これがあなたにとって間違っているように見えるなら、それはhtmlです。私がそれを入力したときは正しかったが、表示されたときはそうではありませんでした。)そのC関数が作成されたら、Cソースコードを生成することはまったく難しくありません。ユーザーが入力した入力フィールドのテキストは、C文字列リテラルに変換されます。安全にするのは難しくありません。 SQLインジェクションを回避する方法としてSQL開発者がそのアプローチを使用しない理由は私を超えています。

「サニタイジング」は完全に欠陥のあるアプローチです。致命的な欠陥は、特定のユーザー入力を違法にすることです。汎用テキストフィールドにのようなテキストを含めることができないデータベースが作成されます。損傷を引き起こすためにSQLインジェクションで使用するテーブルまたは何かをドロップします。私はそれが全く受け入れられないことだと思います。データベースがテキストを格納する場合、データベースはanyテキストを格納できるはずです。そして実用的な欠陥は、消毒剤がそれを正しく行うことができないように見えることです:-(

もちろん、パラメーター化されたクエリは、コンパイルされた言語を使用するプログラマーが期待するものです。文字列の入力があり、それをSQL文字列に変換する必要さえありませんが、パラメーターとして渡すだけで、その文字列に文字が含まれていても損傷を与える可能性はありません。

したがって、コンパイルされた言語を使用する開発者の観点からは、サニタイズは私には決して起こらないものです。消毒の必要性は非常識です。パラメータ化されたクエリは、問題の明白な解決策です。

(私はジョシップの答えが面白いと思いました。彼は基本的にパラメーター化されたクエリでSQLに対する攻撃を停止できると言いますが、JavaScriptインジェクションを作成するために使用されるデータベース内のテキストを持つことができます:-(さて、再び同じ問題があります、そして私はJavascriptがそれに対する解決策を持っているかどうかわかりません。

0
gnasher729

オプション1では、非常に大きな出力サイズにマップしようとしているsize = infinityの入力セットを使用しています。オプション2では、入力を選択したものに制限しました。言い換えると:

  1. [すべての安全なSQLクエリ]を慎重にスクリーニングしてフィルタリング[infinity]
  2. [スコープに限定された事前に検討されたシナリオ]を使用する

他の回答によれば、スコープを無限から離れて、扱いやすいものに制限することにより、パフォーマンス上の利点もあるようです。

0
Mutant Platypus

SQL(特に現代の方言)の1つの有用なメンタルモデルは、各SQLステートメントまたはクエリがプログラムであることです。ネイティブバイナリ実行可能プログラムで最も危険な種類のセキュリティの脆弱性は、攻撃者がプログラムコードをさまざまな命令で上書きまたは変更できるオーバーフローです。

SQLインジェクションの脆弱性は、Cなどの言語のバッファオーバーフローに同型です。歴史は、バッファオーバーフローを防止するのが非常に困難であることを示しています。

オーバーフローの脆弱性を解決する最新のアプローチの重要な側面の1つは、ハードウェアとOSメカニズムを使用して、メモリの特定の部分を実行不可としてマークし、メモリの他の部分を読み取り専用としてマークすることです。 (たとえば、ウィキペディアの 実行可能スペース保護 に関する記事を参照してください。)このようにして、攻撃者がデータを変更できたとしても、攻撃者は挿入されたデータをコードとして処理させることができません。

SQLインジェクションの脆弱性がバッファオーバーフローに相当する場合、NXビットまたは読み取り専用メモリページに相当するSQLは何ですか?答えは次のとおりです。準備されたステートメント。これには、パラメータ化されたクエリに加えて、クエリ以外のリクエストに対する同様のメカニズムが含まれます。準備されたステートメントは、読み取り専用とマークされた特定の部分でコンパイルされるため、攻撃者はプログラムのそれらの部分と、実行不能データ(準備されたステートメントのパラメーター)としてマークされた他の部分を変更できません。これはプログラムコードとして扱われることがないため、乱用の可能性のほとんどが排除されます。

確かに、ユーザー入力をサニタイズすることは良いことですが、本当に安全であるためには、偏執的である(または、同等に、攻撃者のように考える)必要があります。コントロールサーフェイスプログラムテキストの外はそれを行う方法であり、準備されたステートメントはSQLにそのコントロールサーフェイスを提供します。したがって、準備されたステートメント、つまりパラメーター化されたクエリが、セキュリティの専門家の大多数が推奨するアプローチであることは当然のことです。

0
Daniel Pryden

私はすでにこれについてここに書いています: https://stackoverflow.com/questions/6786034/can-parameterized-statement-stop-all-sql-injection/33033576#33033576

ただし、単純にするために:

パラメータ化されたクエリが機能する方法は、sqlQueryがクエリとして送信され、データベースはこのクエリが何を行うかを正確に認識している場合にのみ、ユーザー名とパスワードを単に値として挿入します。つまり、データベースはクエリの処理内容をすでに認識しているため、クエリに影響を与えることはできません。したがって、この場合、「Nobody OR 1 = 1 '-」というユーザー名と空のパスワードが検索されますが、これはfalseになります。

ただし、これは完全なソリューションではなく、JavaScriptをデータベースに挿入できるため、XSS攻撃などの他の問題に影響を与えないため、入力検証を実行する必要があります。次に、これがページに読み込まれると、出力の検証に応じて、通常のJavaScriptとして表示されます。したがって、本当に最善の方法は、入力検証を使用することですが、パラメータ化されたクエリまたはストアドプロシージャを使用してSQL攻撃を阻止します。

0
Josip Ivic