1. ホーム
  2. php

[解決済み] PHPでSQLインジェクションを防ぐにはどうしたらいいですか?

2022-03-18 18:53:27

質問

ユーザ入力が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;--')

このような事態を防ぐには、どうしたらよいのでしょうか?

解決方法は?

その 正しい SQL インジェクション攻撃を回避する方法は、どのデータベースを使用する場合でも、次のとおりです。 データとSQLを分離する そうすれば、データはデータのままであり 解釈されることはない をSQLパーサがコマンドとして処理します。正しくフォーマットされたデータ部分でSQL文を作成することは可能ですが 完全に 詳細を理解するためには、常に プリペアドステートメントとパラメータ化されたクエリを使用します。 これらは、パラメータとは別にデータベースサーバーに送信され、解析されるSQL文です。こうすることで、攻撃者が悪意のあるSQLを注入することは不可能になります。

これを実現するためには、基本的に2つの選択肢があります。

  1. 使用方法 PDO (サポートされているデータベースドライバの場合)。

     $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
     $stmt->execute([ 'name' => $name ]);
    
     foreach ($stmt as $row) {
         // Do something with $row
     }
    
    
  2. 使用方法 MySQLi (MySQL用)です。

     $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
     $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
     $stmt->execute();
    
     $result = $stmt->get_result();
     while ($row = $result->fetch_assoc()) {
         // Do something with $row
     }
    
    

MySQL 以外のデータベースに接続する場合は、ドライバ固有の 2 番目のオプションがあるので、それを参照する(たとえば。 pg_prepare()pg_execute() PostgreSQLの場合)。PDOは世界共通のオプションです。


正しい接続設定

を使用する場合は注意が必要です。 PDO を使用して、MySQL データベースにアクセスします。 リアル プリペアドステートメントは デフォルトで使用されない . これを修正するには、プリペアドステートメントのエミュレーションを無効にする必要があります。を使用して接続を作成する例です。 ピーディーオー があります。

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'password');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

上記の例では、エラーモードは厳密には必要ではありません。 を追加することをお勧めします。 . こうすることで、スクリプトが Fatal Error というのは、何か問題が発生したときです。そして開発者に catch というエラーは throw として PDOException s.

とは 必須 しかし、最初の setAttribute() この行は PDO に対して、エミュレートされたプリペアドステートメントを無効にして リアル 準備されたステートメント。これにより、ステートメントとその値が MySQL サーバーに送信される前に PHP によって解析されないことを確認します (攻撃者が悪意のある SQL を注入する機会を与えないようにします)。

を設定することができますが charset をコンストラクタのオプションで指定することができますが、 「古い」バージョンの PHP (5.3.6 以前) では charset パラメータは無視されます。 を DSN で使用します。


説明

に渡すSQL文は prepare はデータベースサーバーによってパースされ、コンパイルされます。パラメータを指定することで ( ? のような名前付きパラメータ、または :name の例では、) データベースエンジンに、どこをフィルタリングしたいのかを伝えます。そして execute を指定すると、プリペアド・ステートメントに指定したパラメータ値が組み合わされる。

ここで重要なのは、パラメータ値はSQL文字列ではなく、コンパイルされた文と結合されるということです。SQLインジェクションは、スクリプトがデータベースに送信するSQLを作成する際に、悪意のある文字列を含めるように仕向けることで機能します。ですから、実際のSQLをパラメータとは別に送信することで、意図しないものができてしまうリスクを抑えることができるのです。

プリペアドステートメントを使うときに送るパラメータはすべて文字列として扱われます(もちろん、データベースエンジンは最適化を行うので、パラメータが数値になってしまうこともあります)。上の例では、もし $name 変数に 'Sarah'; DELETE FROM employees という文字列を検索することになります。 "'Sarah'; DELETE FROM employees" で終わることはありません。 空のテーブル .

プリペアドステートメントを使うもう一つの利点は、同じセッションで同じステートメントを何度も実行しても、パースとコンパイルは一度だけなので、ある程度のスピードアップが図れることです。

それから、インサートの方法について質問があったので、ここに例を示します(PDOを使用)。

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute([ 'column' => $unsafeValue ]);


プリペアドステートメントは動的クエリに使用できますか?

クエリパラメータにプリペアドステートメントを使用することはできますが、ダイナミッククエリの構造そのものをパラメータ化することはできませんし、特定のクエリ機能をパラメータ化することもできません。

これらの特定のシナリオのために、可能な値を制限するホワイトリストフィルタを使用することが最善です。

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}