PHP で SQL インジェクションを防ぐにはどうすればいいですか? 質問する

PHP で SQL インジェクションを防ぐにはどうすればいいですか? 質問する

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

これを防ぐために何ができるでしょうか?

ベストアンサー1

どのデータベースを使用する場合でも、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の場合):
    PHP 8.2以降ではexecute_query()1 つのメソッドで SQL ステートメントを準備、パラメータをバインド、実行します。

    $result = $db->execute_query('SELECT * FROM employees WHERE name = ?', [$name]);
     while ($row = $result->fetch_assoc()) {
         // Do something with $row
     }
    

    PHP8.1まで:

     $stmt = $db->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 番目のオプションがあります (たとえば、 PostgreSQLpg_prepare()pg_execute()場合)。PDO は汎用的なオプションです。


接続を正しく設定する

PDO

PDOを使用して MySQL データベースにアクセスする場合、実際の準備済みステートメントはデフォルトでは使用されないことに注意してください。これを修正するには、準備済みステートメントのエミュレーションを無効にする必要があります。PDO使用して接続を作成する例は次のとおりです。

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

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

上記の例では、エラー モードは厳密には必要ではありませんが、追加することをお勧めします。このようにして、PDO は をスローすることによってすべての MySQL エラーを通知しますPDOException

ただし、必須なのは最初のsetAttribute()行で、これは PDO にエミュレートされた準備済みステートメントを無効にして、実際の準備済みステートメントを使用するように指示します。これにより、ステートメントと値が MySQL サーバーに送信される前に PHP によって解析されないようにします (攻撃者が悪意のある SQL を挿入する機会を与えません)。

charsetコンストラクタのオプションで設定できますが、PHPの「古い」バージョン(5.3.6以前)では文字セットパラメータを黙って無視しましたDSN で。

MySQLi の

mysqli の場合も同じルーチンに従う必要があります。

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // error reporting
$dbConnection = new mysqli('127.0.0.1', 'username', 'password', 'test');
$dbConnection->set_charset('utf8mb4'); // charset

説明

に渡す SQL 文は、prepareデータベース サーバーによって解析され、コンパイルされます。パラメータ ( または上記の例の?ような名前付きパラメータ:name) を指定することにより、データベース エンジンにフィルタリングする場所を指示します。次に を呼び出すとexecute、準備された文が指定したパラメータ値と結合されます。

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

準備されたステートメントを使用するときに送信したパラメータは、文字列として扱われます(ただし、データベースエンジンは最適化を行うため、パラメータが数値になることもあります)。上記の例では、$name変数に が含まれている場合、'Sarah'; DELETE FROM employees結果は単に文字列 を検索するだけになり"'Sarah'; DELETE FROM employees"、次の結果にはなりません。空のテーブル

準備済みステートメントを使用するもう 1 つの利点は、同じセッションで同じステートメントを何度も実行する場合、解析とコンパイルが 1 回だけ行われるため、速度が向上することです。

ああ、挿入の方法について質問されたので、ここに例を示します (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';
}

おすすめ記事