正しい 使用するデータベースに関係なく、SQLインジェクション攻撃を回避する方法は、SQLからデータを分離することです。 、データがデータのままであり、解釈されないようにする SQLパーサーによるコマンドとして。正しくフォーマットされたデータ部分を使用してSQLステートメントを作成することは可能ですが、完全に 詳細を理解するには、常にプリペアドステートメントとパラメータ化されたクエリを使用する必要があります。 これらは、パラメータとは別にデータベースサーバーに送信されてデータベースサーバーによって解析されるSQLステートメントです。このように、攻撃者が悪意のあるSQLを挿入することは不可能です。
これを実現するには、基本的に2つのオプションがあります。
-
PDOを使用する (サポートされているデータベースドライバーの場合):
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); $stmt->execute([ 'name' => $name ]); foreach ($stmt as $row) { // Do something with $row }
-
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データベースにアクセスするには本物 プリペアドステートメントはデフォルトでは使用されません 。これを修正するには、プリペアドステートメントのエミュレーションを無効にする必要があります。 PDOを使用して接続を作成する例 は:
$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
であるエラー n as PDOException
s。
必須とは何ですか ただし、これは最初のsetAttribute()
です。 line。これは、エミュレートされたプリペアドステートメントを無効にして realを使用するようにPDOに指示します。 プリペアドステートメント。これにより、ステートメントと値がPHPによって解析されてからMySQLサーバーに送信されないようになります(攻撃者が悪意のあるSQLを挿入する可能性がなくなります)。
charset
を設定できますが コンストラクターのオプションでは、「古い」バージョンのPHP(5.3.6より前)文字セットパラメータを黙って無視しました
DSN内。
説明
prepare
に渡すSQLステートメント データベースサーバーによって解析およびコンパイルされます。パラメータ(?
のいずれか)を指定する または: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';
}