PreparedStatementはどのようにしてSQLインジェクションを回避または防止しますか?


122

PreparedStatementsはSQLインジェクションを回避/防止することを知っています。それはどのように行うのですか?PreparedStatementsを使用して構築される最終的なフォームクエリは、文字列になるか、それともそれ以外になりますか?


3
技術的には、JDBC仕様はSQLインジェクションの欠陥がないことを主張していません。影響を受けるドライブを知りません。
トム・ホーティン-

3
@Jayeshブログコンテンツを回答としてここに追加することをお勧めします。ほとんどの答えは、動的SQLクエリの生成と準備されたステートメントの違いを伝えているだけです。彼らは準備されたステートメントがブログよりもうまく機能する理由の問題に取り組んでいません。
Pavan Manjunath

1
回答として追加されました。お役に立てば幸いです。
Jayesh、2015

回答:


78

SQLインジェクションの問題は、ユーザー入力がSQLステートメントの一部として使用されることです。準備済みステートメントを使用することで、ユーザー入力をパラメーターのコンテンツとして(SQLコマンドの一部としてではなく)処理するように強制できます。

しかし、ユーザー入力を準備済みステートメントのパラメーターとして使用せず、代わりに文字列を結合してSQLコマンドを作成する場合、準備済みステートメントを使用する場合でも、SQLインジェクションに対して脆弱です。


1
もちろん、パラメータの一部またはすべてをハードコーディングすることもできます。
タンジェンス2009年

16
例をご覧ください-しかし、ユーザー入力を準備済みステートメントのパラメーターとして使用せず、代わりに文字列を結合してSQLコマンドを作成する場合、準備済みステートメントを使用する場合でもSQLインジェクションに対して脆弱です。
デビッドブレイン2013

4
FWIW PreparedステートメントはJDBCのものではなく、SQLのものです。SQLコンソール内から準備済みステートメントを準備して実行できます。PreparedStatementは、JDBC内からそれらをサポートするだけです。
beldaz 2013

198

同じことを行う2つの方法を検討してください。

PreparedStatement stmt = conn.createStatement("INSERT INTO students VALUES('" + user + "')");
stmt.execute();

または

PreparedStatement stmt = conn.prepareStatement("INSERT INTO student VALUES(?)");
stmt.setString(1, user);
stmt.execute();

「ユーザー」がユーザー入力からのもので、ユーザー入力が

Robert'); DROP TABLE students; --

その後、最初のインスタンスでは、あなたは夢中になります。2つ目は安全で、リトルボビーテーブルが学校に登録されます。


8
したがって、私がそれを正しく理解した場合、実行される2番目の例のクエリは、実際には次のようになります:INSERT INTO student VALUES( "Robert '); DROP TABLE student;-")-または少なくともそのようなもの。これは本当ですか?
最大

18
いいえ、最初のインスタンスでは、そのステートメントを取得します。2番目の例では、 "Robert '); DROP TABLE student;-"をユーザーテーブルに挿入します。
ポールトンブリン

3
それが、2番目の例(「安全な」例)では、文字列Robert 'です。DROP TABLEの学生。-学生テーブルのフィールドに保存されます。他に何か書きましたか?;)
最大

7
申し訳ありませんが、このような混乱のため、引用符のネストは避けようとしています。これが、パラメーター付きのPreparedStatementsが好きな理由です。
ポールトンブリン

59
リトルボビーテーブル。XDグレートリファレンス
アマルゴビナス

128

PreparedStatementがSQLインジェクションを防ぐ方法を理解するには、SQLクエリ実行のフェーズを理解する必要があります。

1.コンパイルフェーズ。2.実行フェーズ。

SQLサーバーエンジンがクエリを受信するたびに、以下のフェーズを通過する必要があります。

クエリ実行フェーズ

  1. 解析および正規化フェーズ: このフェーズでは、クエリの構文とセマンティクスがチェックされます。クエリで使用される参照テーブルと列が存在するかどうかをチェックします。他にも多くのタスクがありますが、詳しくは説明しません。

  2. コンパイルフェーズ: このフェーズでは、select、from、whereなどのクエリで使用されるキーワードが、マシンが理解できる形式に変換されます。これは、クエリが解釈され、対応するアクションが決定されるフェーズです。他にも多くのタスクがありますが、詳しくは説明しません。

  3. クエリ最適化計画: このフェーズでは、クエリを実行する方法を見つけるためのディシジョンツリーが作成されます。クエリを実行できる方法の数と、クエリを実行する各方法に関連付けられているコストがわかります。クエリを実行するための最適なプランを選択します。

  4. キャッシュ: クエリ最適化プランで選択された最適なプランはキャッシュに保存されるため、次回同じクエリが来たときに、フェーズ1、フェーズ2、フェーズ3を再度通過する必要はありません。次回クエリが来たときに、キャッシュで直接チェックされ、そこから取得されて実行されます。

  5. 実行フェーズ: このフェーズでは、指定されたクエリが実行され、データがResultSetオブジェクトとしてユーザーに返されます。

上記のステップでのPreparedStatement APIの動作

  1. PreparedStatementsは完全なSQLクエリではなく、プレースホルダーが含まれています。プレースホルダーは、実行時にユーザーが提供する実際のデータに置き換えられます。

  2. プレースホルダーを含むPreparedStatmentがSQL Serverエンジンに渡されるたびに、以下のフェーズを通過します

    1. 解析および正規化フェーズ
    2. コンパイルフェーズ
    3. クエリ最適化計画
    4. キャッシュ(プレースホルダーを含むコンパイル済みクエリはキャッシュに保存されます。)

UPDATEユーザーセットusername =?およびpassword =?WHERE id =?

  1. 上記のクエリは解析され、特別な扱いとしてプレースホルダーでコンパイルされ、最適化されてキャッシュされます。この段階でのクエリはすでにコンパイルされ、機械が理解できる形式に変換されています。したがって、キャッシュに格納されたクエリはプリコンパイル済みであり、プレースホルダのみをユーザー指定のデータで置き換える必要があると言えます。

  2. 現在、ユーザー提供のデータが入ってくる実行時に、事前コンパイルされたクエリがキャッシュから取得され、プレースホルダーがユーザー提供のデータに置き換えられます。

PrepareStatementWorking

(プレースホルダーがユーザーデータに置き換えられた後、最終クエリは再度コンパイル/解釈されず、SQL Serverエンジンはユーザーデータを純粋なデータとして扱い、解析またはコンパイルが必要なSQLではないことを覚えておいてください。これがPreparedStatementの優れた点です。 )

クエリで再度コンパイルフェーズを実行する必要がない場合、プレースホルダーで置き換えられたデータはすべて純粋なデータとして扱われ、SQL Serverエンジンにとって意味がなく、直接クエリを実行します。

注:クエリ構造を理解/解釈し、意味のある動作を与えるのは、解析フェーズの後のコンパイルフェーズです。PreparedStatementの場合、クエリは1回だけコンパイルされ、キャッシュされたコンパイル済みクエリは常に取得され、ユーザーデータを置き換えて実行します。

PreparedStatementの1回限りのコンパイル機能により、SQLインジェクション攻撃がありません。

ここで例を使用して詳細な説明を取得できます:https : //javabypatel.blogspot.com/2015/09/how-prepared-statement-in-java-prevents-sql-injection.html


3
いい説明
Dheeraj Joshi

4
それが
どの

それはとても役に立ちました。詳しい説明ありがとうございます。
不明

26

PreparedStatementで使用されるSQLは、ドライバーでプリコンパイルされます。その時点から、パラメーターはリテラル値としてドライバーに送信され、SQLの実行可能部分ではありません。したがって、パラメーターを使用してSQLを注入することはできません。ドライバーがそれぞれSQLの解析とコンパイルを実行する必要がないため、PreparedStatements(プリコンパイル+パラメーターのみの送信)のもう1つの有益な副作用は、パラメーターの異なる値でも(ドライバーがPreparedStatementsをサポートしていると仮定して)ステートメントを複数回実行するときのパフォーマンスの向上です。パラメータが変更される時間。


それはそのように実装する必要はありません、そして私はそれがしばしばそうではないと信じています。
トム・ホーティン-

4
実際、SQLは通常、データベースでプリコンパイルされています。つまり、データベース上で実行計画が作成されます。クエリを実行すると、それらのパラメーターを使用してプランが実行されます。追加の利点は、クエリプロセッサが毎回新しいプランをコンパイルしなくても、同じステートメントを異なるパラメーターで実行できることです。
beldaz 2013

3

私は推測する、それは文字列になります。ただし、入力パラメーターはデータベースに送信され、実際のSQLステートメントを作成する前に、適切なキャスト/変換が適用されます。

例を挙げれば、CAST / Conversionが機能するかどうか試してみてください。
それが機能する場合、それから最終的なステートメントが作成される可能性があります。

   SELECT * From MyTable WHERE param = CAST('10; DROP TABLE Other' AS varchar(30))

数値パラメーターを受け入れるSQLステートメントの例を試してください。
次に、文字列変数を渡してみてください(数値パラメーターとして受け入れ可能な数値コンテンツを使用)。エラーが発生しますか?

次に、文字列変数を渡してみます(数値パラメーターとして受け入れられない内容を含む)。何が起こるか見ますか?


3

準備されたステートメントはより安全です。パラメータを指定されたタイプに変換します。

たとえばstmt.setString(1, user);userパラメータを文字列に変換します。

パラメータに実行可能なコマンドを含むSQL文字列が含まれていると仮定します。準備されたステートメントを使用すると、それは許可されません。

これにメタキャラクター(別名自動変換)を追加します。

これにより、より安全になります。


2

SQLインジェクション:ユーザーがSQLステートメントの一部になる可能性のある何かを入力する機会がある場合

例えば:

文字列クエリ=“ INSERT INTO student VALUES( '” + user +“')”

ユーザーが「ロバート」を入力したとき。DROP TABLEの学生。–」入力として、SQLインジェクションが発生します

準備されたステートメントはこれをどのように防ぎますか?

文字列クエリ=“ INSERT INTO student VALUES( '” +“:name” +“')”

parameters.addValue( "name"、user);

=>ユーザーが再度「Robert」と入力した場合); DROP TABLEの学生。–「、入力文字列はリテラル値としてドライバーでプリコンパイルされており、次のようにキャストされると思います。

CAST( 'ロバート'); DROP TABLEの学生。– 'AS varchar(30))

したがって、最後に、文字列は文字通りテーブルの名前として挿入されます。

http://blog.linguiming.com/index.php/2018/01/10/why-prepared-statement-avoids-sql-injection/


1
私が間違えていなければ、CAST(‘Robert’);からの部分CAST(‘Robert’); DROP TABLE students; –‘ AS varchar(30))が壊れて、それがそうであった場合、テーブルをドロップし始めます。これは注入を停止するので、この例はシナリオを説明するのに十分なだけではないと思います。
エクトル・アルバレス

1

PreparedStatement:

1)SQLステートメントのプリコンパイルとDB側のキャッシュにより、全体的に実行が高速になり、同じSQLステートメントをバッチで再利用することができます。

2)引用符やその他の特殊文字の組み込みエスケープによるSQLインジェクション攻撃の自動防止。これには、PreparedStatement setXxx()メソッドのいずれかを使用して値を設定する必要があることに注意してください。


1

この投稿で説明されているように、PreparedStatement文字列をまだ連結している場合、だけでは役に立ちません。

たとえば、1人の不正な攻撃者が次のことを実行できます。

  • すべてのデータベース接続がビジーになるようにスリープ関数を呼び出し、アプリケーションを使用不可にします
  • DBから機密データを抽出する
  • ユーザー認証をバイパスする

SQLだけでなく、JPQLやHQLでも、バインドパラメータを使用していない場合は危険にさらされる可能性があります。

結論として、SQLステートメントを作成するときに文字列連結を使用しないでください。そのために専用のAPIを使用します。


1
PreparedStatementだけではなく、パラメータバインディングを使用することの重要性を指摘していただきありがとうございます。ただし、返信は、SQLインジェクションから保護するために専用APIを使用する必要があることを示しているようです。これはそうではなく、PreparedStatementをパラメーターバインディングと共に使用しても機能するので、再定式化しますか?
Wild Pottok、

-3

準備済みステートメントでは、ユーザーはデータをパラメーターとして入力する必要があります。ユーザーがDROP TABLEやSELECT * FROM USERSなどの脆弱なステートメントを入力した場合、これらはSQLステートメントのパラメーターと見なされるため、データは影響を受けません


精度を下げて選択した回答と同じ回答。
Julien Maret
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.