SQL インジェクションを回避するためにプリペアド ステートメントが使用されていることは知っています。準備されたステートメントの仕組み/原理がどのように機能するのか疑問に思っていますか? 異なるプログラミング言語間で違いはありますか? どの部分が機能するはずで、どの部分がユーザー入力フィールドであるかをどのように検出しますか?
2 に答える
これはセキュリティではなく、むしろ効率性に関するものです。セキュリティはおまけとして付いてきます。サーバーがクエリを実行するための意味のある指示に変換するには、サーバーがクエリを解析する必要があることは確かに認識しています。これは、ソース コードをマシン コードにコンパイルする必要がある通常のプログラムと同じ状況ですが、コンパイル作業は DB サーバーによって行われます。
サーバーは、SQL クエリを受信するたびに、ほとんどの場合、その作業を実行する必要があります。クエリを準備することは、サーバーにそれを伝えることです。
2 番目のアイデアは、クエリをパラメーター化できるようにすることです。多くの場合、プログラムはクエリを数回実行する必要があり、反復ごとに値がわずかに変化するだけです。パラメーターを使用すると、このプロセスをクエリの準備のキャッシュと簡単に組み合わせることができます。
このプロセス全体がどのように機能するかについては、前述のとおり、これは単なるコンパイルです。SQL は解析され、抽象構文ツリーに変換されます。このツリーに多くの変換が適用され、不要な句を削除するか、対象のスキーマの構造をより有効に活用する方法で再構築することにより、より最適なバージョンを生成します。EXECUTE
次に、結果のツリーは、コマンドが発行されるたびにサーバーが実行する命令のストリームに変換されます。パラメータが SQL クエリに配置される場所によっては、値が最適化の実行方法を決定する可能性があるため、最初の解析後にコンパイル手順が遅れる場合があります。
インジェクションが準備されたクエリで機能しない主な理由は、インジェクションが SQL 構文に依存しているためですが、ステートメントが準備された後です。インジェクション データが実際にサーバーに送信された時点で、SQL の解析は完了しているため、構文上の問題は発生しません。パラメーターは、周囲のクエリ内の構文解釈の形式なしで、「そのまま」取得されます。関連するバインディングが表示される式で必要な型にのみ強制されます。
たとえば、従来の注入方法では、ステートメントを短くし、他のコマンドを挿入してから、最後のコマンドを追加して、後続の SQL コードが構文エラーを起こさないようにします。
SELECT * FROM table WHERE x = ? AND k = 1
上記のクエリで、疑問符を次のように置き換えると0; DROP TABLE table
、インジェクションが実現します。ステートメントの意味は本来の意図から逸脱しており、不要なコードを実行するようになりました。ただし、末尾AND k = 1
は構文エラーになるため、たとえば、文字列全体を構文的に修正する別のコマンドを追加する必要があります; SELECT 0 FROM table WHERE 1 = 1
。
このインジェクションは、解析前にプレースホルダーの置換が発生した場合にのみ機能する可能性があります。そうしないと、バインドされた文字列全体が?
、他の構文上の意味を持たない単なる文字列になります。別の型 ( 、 など) への強制ですがint
、enum
エラーが発生する可能性があります。
このトピックに関するウィキペディアのページは、この謙虚な回答よりもはるかに優れた仕事をしており、多くの例と参考文献が掲載されています。
むしろ、データへのアクセスに使用するライブラリに依存します。このライブラリは、特別なエスケープ構文を定義して、クエリのどの部分が置換され、どの部分が静的であるかを定義するために使用します。このような:
string commandText = "UPDATE Customers SET Active = 1 WHERE CustomerID = @ID;";
SqlCommand command = new SqlCommand(commandText, connection);
command.Parameters.Add("@ID", SqlDbType.Int);
command.Parameters["@ID"].Value = customerID;
これは、ADO.NET データ アクセス ライブラリを使用する C# コードです。基本的に、ランタイムの @ID を実際のパラメーター値に置き換えることができます。実際には、キャッシングも行うため、より洗練されているため、まだ展開されていない文字列をサーバーに送信しますが、このキャッシング部分については今のところ忘れることができます。