ストアドプロシージャと準備されたステートメントが、mysql_real_escape_string()
関数よりもSQLインジェクションを防ぐための好ましい最新の方法であるのはなぜですか?
SQLインジェクションの問題は、基本的にロジックとデータの混合であり、データであるべきものがロジックとして扱われます。準備されたステートメントとパラメーター化されたクエリは、ロジックをデータから完全に分離することにより、ソースでこの問題を実際に解決するため、注入はほぼ不可能になります。文字列エスケープ関数は実際には問題を解決しませんが、特定の文字をエスケープすることにより危害を回避しようとします。文字列エスケープ関数を使用すると、どんなに優れていても、エスケープされていない文字がそれを通過できる可能性のあるEdgeケースの心配が常にあります。
@Sebastianのコメントで述べられているように、もう1つの重要な点は、準備されたステートメントを使用してアプリケーションを作成するときに一貫性を保つことがはるかに容易になることです。準備されたステートメントの使用は、アーキテクチャ的には従来の方法とは異なります。文字列連結の代わりに、パラメータをバインドしてステートメントを構築します(例: PDOStatement :: bindParam PHPの場合)、その後に別の実行ステートメント(PDOStatement :: execute)。ただし、mysql_real_escape_string()
では、クエリを実行するだけでなく、最初にその関数を呼び出すことを忘れないでください。アプリケーションにSQLクエリを実行する1000のエンドポイントがあり、そのうちの1つでもmysql_real_escape_string()
を呼び出すのを忘れた場合、または不適切に呼び出した場合、データベース全体が開いたままになる可能性があります。
ここでの主な質問は、文字列のエスケープが他の方法ほど良くない理由です。
答えは、一部のEdgeケースでは、エスケープされていても注射が通り抜けることができるということです。 Stack Overflowには ここにいくつかの例があります があります。
SQLiがユーザー入力をエスケープしないように安全に保護することはできますが、常に十分ではない場合があることに注意することが重要です。このひどい例では、エスケープにもかかわらず、SQLインジェクションを成功させるために引用符は必要ありません。
_<?php
/*
This is an example of bad practice due to several reasons.
This code shall never be used in production.
*/
$post_id = mysql_real_escape_string($_GET['id']);
$qry = mysql_query("SELECT title,text FROM posts WHERE id=$post_id");
$row = mysql_fetch_array($qry);
echo '<h1>'.$row['title'].'</h1>';
echo $row['text'];
_
では、.php?id=-1+UNION+ALL+SELECT+1,version()
を使用するとどうなりますか?クエリの様子を見てみましょう。
_SELECT title,text FROM posts WHERE id=-1 UNION ALL SELECT 1,version()
_
確かにそれを修正する他の方法があります(つまり、引用符を使用してエスケープまたはintキャストする)が、準備されたステートメントを使用すると、これらのことを見落とす可能性が低くなり、ドライバーがクエリにユーザー入力を含める方法を気にするようになります(この例は簡単に修正できるように見えますが、基本的に、データベースからのデータがデータベースの一部として将来使用されることを認識して、SQLクエリの一部をデータベースに保存するマルチレベルSQLインジェクションがあります別のクエリ)。
転ばぬ先の杖。
PHP=に対して言及される一般的な問題はmysqli_real_escape_string()
です(どれくらいの長さであるかを確認してください!命名法と一致していないのでしょうか?)が、ほとんどの人はそれを理解していませんPHP APIは 単にMySQL APIを呼び出す です。そして、それは何をしているのですか?
この関数は、SQLステートメントで使用する正当なSQL文字列を作成します
言い換えると、この関数を使用しているときは、MySQLに値をサニタイズしてSQLで「安全」に使用するように要求しています。この点については、すでにデータベースに作業を依頼しています。プロセス全体は次のようになります
準備済みステートメントを使用するとき、データベースに、これらを異なる順序で実行する必要があることを伝えています。
データを個別に送信しているため、エスケープと同じ量の作業を行っていますが、SQLに信頼できないデータがないの利点があります。そのため、エンジンは、提供されたデータをSQL命令と混同することはありません。
ここには隠れた利点もあります。気づかないかもしれませんが、手順1が完了したら、手順2と3を繰り返し実行できます。ただし、手順2と3はすべて同じクエリを実行する必要があり、データが異なるだけです。
$prep = $mysqli->prepare('INSERT INTO visits_log(visitor_name, visitor_id) VALUES(?, ?)');
$prep->bind_param('si', $visitor_name, $visitor_id); // this function passes by reference
foreach($_POST['visitor_list'] as $visitor_id => $visitor_name) $prep->execute();
このクエリは、毎回解析のオーバーヘッドを追加するのではなく、データをループして繰り返し送信するだけで済むというメリットがあります。
mysql_real_escape_string
を使用しない1つの理由:非推奨
mysql_real_escape_string
(PHP 4> = 4.3.0、PHP 5)
mysql_real_escape_string — SQLステートメントで使用するために文字列内の特殊文字をエスケープする
警告
この拡張機能はPHP 5.5.0で廃止され、PHP 7.0.0で削除されました。代わりに、MySQLiまたはPDO_MySQL拡張機能を使用する必要があります。また、MySQL:APIガイドの選択と関連するFAQ詳細については、この関数の代わりに以下を含めます:
mysqli_real_escape_string() PDO::quote()
また、エスケープは文字セットに依存することも説明しています:
Mysql_real_escape_string()に影響を与えるには、文字セットをサーバーレベルまたはAPI関数mysql_set_charset()で設定する必要があります。詳細については、文字セットの概念セクションを参照してください。
また、たとえばストアドプロシージャが動的SQLを含んでいる場合など、ストアドプロシージャは常に「安全」ではないことも付け加えておきます。不適切なコーディング手法からは保護されません。しかし、実際には、ストアドプロシージャを使用するかどうかはあなた次第ですparameterizedステートメントを使用する必要があります。
いくつかの良い答えはすでにあり、私はさらにいくつかの明確化を提供するつもりです:
エスケープ
mysql_real_escape_string
は安全に使用できます。エスケープに関するアドバイスは、データベースに適したエスケープ関数を使用することです。データベースごとに少し異なる関数が必要です。微妙なセキュリティ問題のほとんどは、自分のサニタイザー(例:'
-> ''
)を使用していて、特殊なケースを実現していない人々に起因しています。値を引用符で囲んでmysql_real_escape_string
を正しく使用する必要はありますが、準備されたステートメントを含むほとんどのディフェンスでは、これ(引用符ではなく原則)が当てはまります。
準備されたステートメント
準備済みステートメントは、SQLインジェクションを停止するための非常に良い方法です。ただし、それでも誤解される可能性があります。SQLステートメントを動的に作成し、そこから準備済みステートメントを作成する人を見かけることがあります。準備されたステートメントを使用するように指示したので、これはまだ安全ではないと彼らに言ったとき、彼らは最も動揺しました!また、準備済みステートメントを使用することになるコードは、少し不快です。大したことではなく、わずかに審美的に不快です。
一部のデータベースコネクタは、内部でエスケープを使用して準備されたステートメントを実装します(Python/Postgresのpsychopgがこれを行うと思います)。他のデータベース(Oracleもその1つです)では、プロトコルはパラメーターを特定にサポートしているため、クエリから完全に分離されています。
ストアドプロシージャ
ストアドプロシージャは必ずしもSQLインジェクションを停止するわけではありません。一部のコネクターでは、準備済みステートメントのように直接呼び出すことができます。ただし、SQLから呼び出されることが多く、データをSQLに安全に渡す必要があります。ストアドプロシージャの内部にSQLインジェクションがある可能性もあります。そのため、準備されたステートメントで呼び出された場合でも危険です。このアドバイスは、主に混乱から生じていると思います。通常、セキュリティ担当者はDBAではなく、準備されたステートメント、パラメーター化されたクエリ(準備されたステートメントの別の名前)、およびストアドプロシージャ(何か異なるもの)の違いについて曖昧です。ストアドプロシージャにはセキュリティ上の利点があるため、このアドバイスは続いています。これらを使用すると、DBへの安全な直接アクセスを可能にするため、または多層防御メカニズムとして、DBレイヤーでビジネスロジックを適用できます。
タイプセーフなクエリ構文
SQLインジェクションを停止することをお勧めする方法は、タイプセーフなクエリメカニズムを使用することです。これらの多くは、ほとんどのORM(Hibernate、SQLAlchemyなど)や、LINQやjOOQなどの一部の非ORMライブラリ/機能を含みます。これらはSQLインジェクションから保護し、見栄えの良いコードを提供します。また、それらを誤って使用してSQLインジェクションが発生することも困難です。個人的には、私はORMの大 fan ですが、全員がそうというわけではありません。セキュリティ上の重要な点は、ORMを使用することではなく、タイプセーフなクエリシステムを使用することです。
わかりました。これで問題が解決しました。良い質問。
InfoSec以外の追加の引数として、パラメーターを使用すると、クエリを高速化し、メモリ使用量を削減できる可能性があります。
パラメータを使用すると、データとクエリが分離されます。これは、非常に大きな文字列とバイナリフィールドを挿入する場合に特に重要です(文字列全体をエスケープ関数で処理する必要があります(これにより、文字列全体が拡張され、文字列全体をコピーする必要がある場合があります)。 SQLエンジンによって再度解析されますが、パラメーターは単に渡され、解析されない必要があります)。
一部のデータベースコネクタでは、パラメータを使用したチャンク化も可能です。これにより、文字列全体を一度にメモリに保持する必要がなくなります。データはSQL文字列の一部であり、SQL文字列はSQL文字列なしでは解析できないため、文字列連結を使用したチャンク化は決して選択肢にはなりません。
パラメータを使用するよりもエスケープ関数を使用して文字列連結を使用するメリットはなく、文字列連結でパラメータを使用する場合は複数あるため、論理的にはパラメータに向かっています。これにより、エスケープ関数が非推奨になり、将来的に潜在的なセキュリティ問題が発生し、それらを使用しない必要性が高まります。
ストアドプロシージャと準備されたステートメントの両方が、パラメーターを使用して潜在的なスコープを制限します。 call GetUserWithName ('jrmoreno');
はless機会を提供するのではなく、クエリを混乱させる機会を提供します。準備されたステートメントによるパラメーター化されたストアドプロシージャは、プレーンなストアドプロシージャよりもcall GetUserWithName (@userName);
が少ないことに注意してください。
また、ストアドプロシージャを使用すると、別の種類のSQLインジェクションが可能になります。ストアドプロシージャ内の動的SQLです。
準備されたステートメントを使用する理由については、準備されたステートメントは、パラメーターの型とサイズが固定されているバイナリプロトコルです。サーバーによって処理されるとき、数値が数値以外であったり、文字列がその境界を超えて拡張され、より多くのSQLコマンドとして扱われることは不可能です(その文字列が内部で動的SQLを作成するために使用されていない限り) SQLエンジン)。
文字列をエスケープしても、同じレベルの保証は得られません。
特定の日にソフトウェアを作成しているデータベースでストアドプロシージャや準備されたステートメントを常に使用するというポリシーを設定できます。私のc#コード(たとえば)は、現在の雇用主が選択するデータベースに関係なく同じように見えるので、準備されたステートメントなどを使用していないときに簡単に見つけることができます。
準備されたステートメントは重要ではありませんが、重要なのはimportですnever文字列演算子を使用してデータベースに送信するSQLを作成します。
mysql_real_escape_string()
は、1つのデータベースのオプションにすぎない空間的なケースなので、コード化しなければならない次のデータベースでは役に立たない可能性があることを学ぶ方法はありますか?
ストアドプロシージャは、インジェクションを含む一部の攻撃に対する防御策として機能するだけでなく、優れています。セキュリティの事実上の「証明」により、より多くのことをより安全に行うことができるため、優れています。一方、単に文字列をエスケープするだけでは、その「証明」を提供することはできません。
どうして?
クエリ文字列は、データが適切にエスケープされていても、テキストの一部です。テキストは本来不快です。テキストは解析され、時間がかかり、多くの点で失敗する可能性があります。その後、データベースエンジンはsomethingを実行します。それは何をするためのものか?あなたは決して100%確実であることができません。
文字列エスケープ関数の呼び出しを忘れるか、文字列エスケープ関数が悪用される可能性のある方法で欠陥がある可能性があります。ありそうもありませんが、100%正しくエスケープされたクエリであっても、原則として別のことを行うことは可能です。確かに、アクセス制御などがあり、被害の可能性を制限していますが、何でもかまいません。原則として、実行可能なコードを送信することとそれほど変わらない文字列をデータベースエンジンにスローし、それからsomething、うまくいけば、まさにあなたが望むものです。
一方、ストアドプロシージャはone指定された操作を正確にカプセル化します。あなたが望むものを送ってください、起こる実際の操作はすでに定義されています。
ストアドプロシージャは、原則として、正確に1回だけ解析および最適化され、(おそらく、必ずしもではないが、少なくとも理論的には)非常に高速な表現(たとえば、何らかの形式のバイトコード)にコンパイルされます。
だから...あなたは文字列をエスケープするのを忘れましたか?誰も気にしない。文字列エスケープ関数が正しく機能しませんか?誰も気にしない。ストアドプロシージャは、以前は指示されなかった処理を実行できません。最悪の事態は、サーバーを悪用しようとするユーザーが、ユーザー名などのゴミをなんとか挿入していることです。誰も気にしない。
したがって、セキュリティが大幅に向上し、サーバーは必要な作業が少なくなります(つまり、クエリの実行が速くなります)。あなたは両端で勝ちます(通常はトレードオフがあるので、これは珍しいことです)。
加えて、純粋に「美的」観点から、それが実際に重要であるということではなく、物事は彼らがすべきである方法です。データベースは常に渡されるdataであり、その代わりに、独自の裁量で、ある種のプログラムを実行します。
戦略的なレベルでは、問題は警戒に関係しています。コードを書くとき、セキュリティ(または安全、またはその他の形式の品質/パフォーマンス)の問題を確実に防止するために何か特別なことをする必要がある場合は、実際にそれを行う必要があります。
これが、「エスケープ」機能を使用する上での主な問題だと思います。
問題をさらに複雑にしているのは、「エスケープ」機能がクエリの構築/評価自体の外で実行されるため、エスケープの有無に関するすべてのクエリを監査できる簡単または効率的な方法がないということです。
この弱点を回避するには、escape/sql構成を関数に変換する必要があります-これは基本的に準備済みステートメントと同じであるか、抽象レイヤーを使用して完全にsql(たとえば、ある種のORM)を使用します。
SQLモンキーとして、どちらのオプションも素晴らしいものではありません。特に、複雑なSQL構造(たとえば、同じテーブルを2回使用することを含む、いくつかの左結合を含む3フィールドの主キーテーブル、別名:ツリーベースの構造では珍しくない)を扱う場合。
私は理論的には話していません。これは実際的な経験です。私たちはエスケープメカニズムの使用を開始し、その後、幸運にもペンで引っ掛かりました。テストエージェンシー-エスケープの欠落を発見したユーザー(手元のケースは、エスケープされていないセッションCookieの値でした)。彼らは「select sleep(10);」を注入することでそれを実証しました。 -注射のテスト/デモンストレーションの見事な方法。
(少し話題から外れると、開発チームが独自のコードのインジェクションテストを作成することは不可能に近づきます。私たちは物事を1つの位置からのみ見る傾向があります。これが、会社がサードパーティの完全に独立したペンを常に推奨する理由です。テスト代理店。)
あ、はい。スタンドアロンとしてのescape()は、インジェクション保護を確実にする非常に弱い方法です。常にクエリ構築の一部として保護を組み込む必要があります。これは、警戒に依存せず、SQLインジェクション攻撃が確実に行われるようにするために、デフォルトで強制されることが重要であるためです。
mysql_real_escape_string()
はSQLインジェクションに対する保護を意図したものではないことを理解する必要があります。害を減らし、潜在的な攻撃ベクトルを制限することを意図したハックにすぎません。 mysql_real_escape_string()
の名前または公式ドキュメントには、SQLインジェクションを防止するためにこの関数を使用することは記載されていません。
SQLインジェクションは、変数データをSQL文字列の一部にすることを許可した場合に発生します。動的データを使用してSQLを構築する場合、SQLが壊れないようにすることは困難です。不正な入力はSQLを破壊し、データに害を及ぼすか、情報を漏らす可能性があります。
SQLは、他のコードと同じように、定数でなければなりません。 SQLで変数入力を許可すると、変数入力を伴うeval()
と同様の攻撃に対して脆弱になります。何が実行されているかは決してわかりません。
これを防ぐには、SQLが一定であり、入力によって変化しないことを確認する必要があります。これに対する解決策はプレースホルダーです。データをパラメーター化し、リテラルが配置される場所でプレースホルダーを使用しました。エスケープは必要ありません。次に、そのようなステートメントを準備し、実行中にデータを個別に渡します。 SQLは常に同じであり、データはSQLに影響を与え、その動作を変更する方法がありません。
ストアドプロシージャは、SQLインジェクションを防ぐ安全な方法ではありません。エスケープと同様に、攻撃ベクトルを制限するだけです。