ユーザーが SQL ステートメントに直接挿入されるクエリを入力した場合、次の例のように、アプリケーションは SQL インジェクションに対して脆弱になります:
<code><span>$unsafe_variable</span> = <span>$_POST</span>[<span>'user_input'</span>]; mysql_query(<span>"INSERT INTO table (column) VALUES ('"</span> . <span>$unsafe_variable</span> . <span>"')"</span>);</code>
これは、ユーザーが VALUE"); DROP TABLE のようなものを入力できるためです。 table; - 、クエリは次のようになります:
<code><span><span>INSERT</span><span>INTO</span><span>table</span> (<span>column</span>) <span>VALUES</span>(<span>'VALUE'</span>);</span><span><span>DROP</span><span>TABLE</span><span>table</span>;</span>'</code>
この状況を防ぐにはどうすればよいでしょうか?Theo の答えを見てみましょう
準備されたステートメントとパラメーター化されたクエリを使用します。パラメータを含む SQL ステートメントはデータベース サーバーに送信され、解析されます。攻撃者が悪意を持って SQL を挿入することは不可能です。
この目標を達成するには、基本的に 2 つのオプションがあります:
1. PDO (PHP データ オブジェクト) を使用する
<code><span>$stmt</span> = <span>$pdo</span>->prepare(<span>'SELECT * FROM employees WHERE name = :name'</span>); <span>$stmt</span>->execute(<span>array</span>(<span>':name'</span> => <span>$name</span>)); <span>foreach</span> (<span>$stmt</span><span>as</span><span>$row</span>) { <span>// do something with $row</span> }</code>
2. mysqli
<code><span>$stmt</span><span>=</span><span>$dbConnection</span><span>-></span>prepare(<span>'SELECT * FROM employees WHERE name = ?'</span>); <span>$stmt</span><span>-></span>bind_param(<span>'s'</span>, <span>$name</span>); <span>$stmt</span><span>-></span>execute(); <span>$result</span><span>=</span><span>$stmt</span><span>-></span>get_result(); <span>while</span> (<span>$row</span><span>=</span><span>$result</span><span>-></span>fetch_assoc()) { <span>// do something with $row</span> }</code>
PDO (PHP データ オブジェクト) を使用する
PDO を使用して MySQL データベースにアクセスする場合、デフォルトでは実際のプリペアド ステートメントは使用されないことに注意してください。この問題を解決するには、準備されたステートメントのエミュレーションを無効にする必要があります。 PDO を使用して接続を作成する例は次のとおりです。
<code><span>$dbConnection</span><span>=</span><span>new</span> PDO(<span>'mysql:dbname=dbtest;host=127.0.0.1;charset=utf8'</span>, <span>'user'</span>, <span>'pass'</span>); <span>$dbConnection</span><span>-></span>setAttribute(PDO<span>::ATTR_EMULATE_PREPARES</span>, <span>false</span>); <span>$dbConnection</span><span>-></span>setAttribute(PDO<span>::ATTR_ERRMODE</span>, PDO<span>::ERRMODE_<strong>Exception</strong></span>);</code>
エラー モード ERRMODE は、上記の例では必ずしも必要ではありませんが、追加することをお勧めします。このメソッドは、致命的なエラーが発生した場合でもスクリプトを停止しません。そして、開発者にエラーをキャッチする機会を与えます (PDO Exception 例外がスローされた場合)。
setAttribute() 行は必須で、エミュレートされたプリペアド ステートメントを無効にし、実際のプリペアド ステートメントを使用するように PDO に指示します。これにより、ステートメントと値が MySQL データベース サーバーに送信される前に PHP によって解析されなくなります (攻撃者が悪意のある SQL を挿入する可能性はなくなります)。
もちろん、コンストラクター オプションで文字セット パラメーターを設定できます。特に、「古い」PHP バージョン (5.3.6) では DSN の文字セット パラメーターが無視されることに注意してください。
渡した SQL プリペアド ステートメントがデータベース サーバーによって解析およびコンパイルされるとどうなりますか?文字 (上記の例では、? や :name など) を指定して、データベース エンジンにフィルター対象を伝えます。その後、execute を呼び出して、指定したパラメーター値と組み合わせて準備されたステートメントを実行します。
ここで最も重要なことは、パラメータ値が SQL 文字列ではなく、プリコンパイルされたステートメントと結合されることです。したがって、SQL インジェクションは、悪意のある文字列を含む SQL スクリプトを不正に作成し、実際の文字列をデータベースに送信することによって機能します。個別の SQL パラメータを使用すると、リスクが軽減されます。プリペアド ステートメントを使用する場合、送信するパラメータは文字列としてのみ扱われます (ただし、データベース エンジンはパラメータの最適化を行う可能性がありますが、これはもちろん上記では Number になる可能性があります)。たとえば、変数 $name に 'sarah';DELETE * FROMemployees が含まれている場合、結果は検索文字列 "'sarah';DELETE * FROMemployees" のみとなり、空のテーブルは得られません。
プリペアド ステートメントを使用するもう 1 つの利点は、同じセッション内で同じステートメントを複数回実行した場合、解析とコンパイルが 1 回だけで済み、速度がある程度向上することです。
ああ、挿入方法を尋ねられたので、ここに例を示します (PDO を使用):
<code><span>$preparedStatement</span><span>=</span><span>$db</span><span>-></span>prepare(<span>'INSERT INTO table (column) VALUES (:column)'</span>); <span>$preparedStatement</span><span>-></span>execute(<span>array</span>(<span>':column'</span><span>=></span><span>$unsafeValue</span>));</code>
上記は、PHP で SQL インジェクションを防ぐ方法 (2) を、関連する内容も含めて紹介しました。PHP チュートリアルに興味のある友人の参考になれば幸いです。