PHP で SQL インジェクション攻撃を防ぐにはどうすればよいですか?
P粉939473759
2023-08-22 10:23:01
<p>ユーザー入力を変更せずに SQL クエリに挿入すると、次の例に示すように、アプリケーションは SQL インジェクション攻撃に対して脆弱になります。
<pre class="lang-php prettyprint-override"><code>$unsafe_variable = $_POST['user_input'];
mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
</code></pre>
<p>これは、ユーザーが <code>value'); DROP TABLE table;--</code> のようなものを入力できるため、クエリは次のようになります。
<pre class="brush:php;toolbar:false;">INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')</pre>
<p>これを防ぐにはどうすればよいですか? </p>
パラメータ化されたクエリを使用するには、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() メソッドを使用して、パラメータを準備、バインドし、SQL ステートメントを実行できます。 リーリー
PHP8.1 より前:
MySQL 以外のデータベースに接続している場合は、2 番目のドライバー固有のオプションを参照できます (たとえば、PostgreSQL の場合は、
pg_prepare())。 PDO はユニバーサル オプションです。
PDO接続を正しく設定してください
を使用して MySQL データベースにアクセスする場合、実際の
プリペアド ステートメントはデフォルトでは使用されないことに注意してください。この問題を解決するには、準備されたステートメントのシミュレーションを無効にする必要があります。以下は、PDO: を使用して接続を作成する例です。 リーリー 上記の例では、エラー モードは厳密には必須ではありませんが、 追加することをお勧めします。このようにして、PDO は PDOException をスローしてすべての MySQL エラーを通知します。
ただし、 が行う必要があるのは、最初の行 setAttribute()
プリペアド ステートメントを使用するように指示します。これにより、ステートメントと値が MySQL サーバーに送信される前に PHP によって解析されなくなります (そのため、潜在的な攻撃者は悪意のある SQL を注入できません)。です。これは、PDO に、シミュレートされたプリペアド ステートメントを無効にし、
実際のコンストラクターのオプションで charset
を設定できますが、PHP の「古い」バージョン (5.3.6 より前)
は DSN の charset 引数を黙って無視することに注意することが重要です。 ###。 Mysqlimysqli の場合、同じルーチンに従う必要があります:
に渡す SQL ステートメントは、データベース サーバーによって解析され、コンパイルされます。パラメーター (上記の例では、リーリー
###説明する### prepare?
または:name
のような名前付きパラメーター) を指定することにより、フィルターする場所をデータベース エンジンに指示します。次に、execute
を呼び出すと、準備されたステートメントが指定されたパラメーター値と結合されます。
ここで重要なのは、SQL 文字列との組み合わせではなく、パラメーター値とコンパイルされたステートメントの組み合わせです。 SQL インジェクションは、データベースに送信する SQL を作成するときに、スクリプトをだまして悪意のある文字列を含めることによって機能します。したがって、実際の SQL をパラメータとは別に送信することで、予期しない結果が発生するリスクを制限できます。
ああ、挿入の操作方法を尋ねられたので、ここに例を示します (PDO を使用):
プリペアド ステートメントをクエリ パラメーターに使用することはできますが、動的クエリ自体の構造はパラメーター化できず、特定のクエリ関数はパラメーター化できません。
これらの特定のシナリオのベスト プラクティスは、ホワイトリスト フィルターを使用して、可能な値を制限することです。
プリペアド ステートメントを使用して送信されたパラメータはすべて文字列として扱われます (ただし、データベース エンジンはパラメータに対して何らかの最適化を実行するため、パラメータが数値になる可能性があります)。上記の例では、
プリペアド ステートメントを使用するもう 1 つの利点は、同じセッション内で同じステートメントが複数回実行された場合、解析とコンパイルが 1 回だけ行われるため、速度がある程度向上することです。$name
変数に'Sarah'; DELETE FROMemployees
が含まれている場合、結果は検索文字列"'Sarah'; DELETE FROMemployee のみになります。 "
、空のテーブルは得られません。リーリー
準備されたステートメントは動的クエリに適していますか?
リーリー