ユーザー入力が変更されずに SQL クエリに挿入される場合、次の例に示すように、アプリケーションは SQL インジェクションに対して脆弱になります。
$unsafe_variable = $_POST['user_input']; mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
これは、ユーザーが value'); DROP TABLE table;--
のようなものを入力できるため、クエリは次のようになります。
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
これを防ぐにはどうすればよいでしょうか?
パラメータ化されたクエリを使用するには、Mysqli または PDO を使用する必要があります。 mysqli を使用して例を書き直すには、次のものが必要になります。
リーリー読む必要がある重要な関数は
です。mysqli::prepare
.また、他の人が提案しているように、PDO.
のようなものを使用することもできます。あなたが質問しているケースは非常に単純ですが、より複雑なケースではより高度な方法が必要になる可能性があることに注意してください。特に:###
ユーザー入力に基づいて SQL 構造を変更したい場合、パラメータ化されたクエリは役に立ちません。また、- mysql_real_escape_string
には必要なエスケープが含まれていません。この場合、ユーザーの入力をホワイトリストに渡して、「安全な」値のみが許可されるようにする方が良いでしょう。
どのデータベースを使用する場合でも、SQL インジェクション攻撃を回避する正しい方法は、データを SQL から分離することですこれにより、データは依然としてデータのままであり、>決して SQL パーサーによってコマンドとして解釈されません。適切にフォーマットされたデータ部分を含む SQL ステートメントを作成することは可能ですが、詳細がまったく理解できない場合は、常にプリペアド ステートメントとパラメーター化されたクエリを使用する必要があります。は、パラメーターとは別にデータベース サーバーに送信され、データベース サーバーによって解析される SQL ステートメントです。これにより、攻撃者が悪意のある SQL を挿入することは不可能になります。
これを実現するには基本的に 2 つのオプションがあります:PDOの使用 (サポートされているデータベース ドライバーの場合):リーリー
の使用 (MySQL の場合):PHP 8.2以降では、
リーリーexecute_query()を使用して、1 つのメソッドで準備、パラメータのバインド、SQL ステートメントの実行を行うことができます。 リーリー PHP8.1 まで:
MySQL 以外のデータベースに接続している場合は、2 番目のドライバー固有のオプションを参照できます (たとえば、PostgreSQL の場合は
pg_prepare()) 。 PDO はユニバーサル オプションです。
PDO接続を正しく設定してください
PDO
を使用して MySQL データベースにアクセスする場合、
実際のプリペアド ステートメントはデフォルトでは使用されないことに注意してください。この問題を解決するには、準備されたステートメントのシミュレーションを無効にする必要があります。PDOを使用して接続を作成する例は次のとおりです。リーリー上記の例では、エラー モードは厳密には必要ありませんが、追加することをお勧めします。このように、PDO はPDOExceptionをスローすることで、すべての MySQL エラーを通知します。
ただし、forcingは最初の
実際のsetAttribute()
行で、シミュレートされたプリペアド ステートメントを無効にし、プリペアド ステートメント ステートメントを使用するように PDO に指示します。これにより、ステートメントと値が MySQL サーバーに送信される前に PHP によって解析されなくなります (攻撃者になる可能性のある者に悪意のある SQL を注入する機会を与えません)。コンストラクターのオプションで
Mysqli文字セット
を設定できますが、PHP の「古い」バージョン (5.3.6 より前)は文字セットを黙って無視することに注意することが重要です。パラメーター ###。
preparemysqli の場合も同じルーチンに従う必要があります:
リーリーイラストに渡す SQL ステートメントは、データベース サーバーによって解析され、コンパイルされます。パラメーター (
?または上記の例の
:name
などの名前付きパラメーター) を指定して、データベース エンジンにフィルターする場所を伝えます。次に、execute
を呼び出すと、準備されたステートメントが指定したパラメーター値と結合されます。
ここで重要なのは、パラメーター値が SQL 文字列ではなく、コンパイルされたステートメントと組み合わされるということです。 SQL インジェクションは、データベースに送信する SQL を作成するときに、スクリプトをだまして悪意のある文字列を含めることによって機能します。したがって、実際の SQL をパラメータとは別に送信することで、予期しない結果が生じるリスクを制限できます。
準備されたステートメントを使用するときに送信するパラメーターはすべて文字列として扱われます (ただし、データベース エンジンが最適化を行う可能性があるため、パラメーターは最終的に数値として扱われることもあります)。上記の例では、
$name
変数に'Sarah'; DELETE FROMEmployees
が含まれている場合、結果は単に検索文字列"'Sarah'; DELETE FROMEmployees"
になります。空のテーブルが残ることはありません。プリペアド ステートメントを使用するもう 1 つの利点は、同じセッション内で同じステートメントを複数回実行しても、解析とコンパイルが 1 回だけ行われるため、速度が向上することです。
ああ、挿入方法を尋ねられたので、ここに例を示します (PDO を使用):
リーリー準備されたステートメントは動的クエリに使用できますか?
プリペアド ステートメントをクエリ パラメーターとともに使用することはできますが、動的クエリ自体の構造はパラメーター化できず、特定のクエリ関数もパラメーター化できません。
これらの特定のシナリオの場合、最適なアプローチは、ホワイトリスト フィルターを使用して、可能な値を制限することです。
リーリー