PHPMySQLプリペアドステートメント
プリペアドステートメントは、SQLインジェクションに対して非常に役立ちます。
プリペアドステートメントとバインドされたパラメータ
プリペアドステートメントは、同じ(または類似の)SQLステートメントを高効率で繰り返し実行するために使用される機能です。
プリペアドステートメントは基本的に次のように機能します。
- 準備:SQLステートメントテンプレートが作成され、データベースに送信されます。パラメータと呼ばれる特定の値は指定されていません(「?」というラベルが付いています)。例:INSERT INTO MyGuests VALUES(?、?、?)
- データベースは、SQLステートメントテンプレートのクエリ最適化を解析、コンパイル、および実行し、結果を実行せずに保存します
- 実行:後で、アプリケーションが値をパラメーターにバインドし、データベースがステートメントを実行します。アプリケーションは、さまざまな値を使用して、ステートメントを何度でも実行できます。
SQLステートメントを直接実行する場合と比較して、プリペアドステートメントには3つの主な利点があります。
- プリペアドステートメントは、クエリの準備が1回だけ行われるため、解析時間を短縮します(ただし、ステートメントは複数回実行されます)。
- バインドされたパラメーターは、クエリ全体ではなく、毎回パラメーターのみを送信する必要があるため、サーバーへの帯域幅を最小限に抑えます。
- プリペアドステートメントは、後で別のプロトコルを使用して送信されるパラメータ値を正しくエスケープする必要がないため、SQLインジェクションに対して非常に役立ちます。元のステートメントテンプレートが外部入力から派生していない場合、SQLインジェクションは発生しません。
MySQLiで準備されたステートメント
次の例では、MySQLiでプリペアドステートメントとバインドされたパラメータを使用しています。
例(プリペアドステートメントを使用したMySQLi)
<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";
// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
// prepare and bind
$stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $firstname, $lastname, $email);
// set parameters and execute
$firstname = "John";
$lastname = "Doe";
$email = "[email protected]";
$stmt->execute();
$firstname = "Mary";
$lastname = "Moe";
$email = "[email protected]";
$stmt->execute();
$firstname = "Julie";
$lastname = "Dooley";
$email = "[email protected]";
$stmt->execute();
echo "New records created successfully";
$stmt->close();
$conn->close();
?>
上記の例から説明するコード行:
"INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)"
SQLでは、整数、文字列、double、またはblob値に代入する疑問符(?)を挿入します。
次に、bind_param()関数を見てください。
$stmt->bind_param("sss", $firstname, $lastname, $email);
この関数は、パラメーターをSQLクエリにバインドし、データベースにパラメーターが何であるかを通知します。「sss」引数は、パラメーターであるデータのタイプをリストします。s文字は、パラメータが文字列であることをmysqlに通知します。
引数は、次の4つのタイプのいずれかになります。
- i-整数
- d-ダブル
- s-文字列
- b-BLOB
パラメータごとにこれらの1つが必要です。
予想されるデータのタイプをmysqlに通知することで、SQLインジェクションのリスクを最小限に抑えます。
注:外部ソース(ユーザー入力など)からデータを挿入する場合は、データをサニタイズして検証することが非常に重要です。
PDOで準備されたステートメント
次の例では、PDOでプリペアドステートメントとバインドされたパラメーターを使用しています。
例(プリペアドステートメントを使用したPDO)
<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDBPDO";
try {
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
// set the PDO error mode to exception
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// prepare sql and bind parameters
$stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email)
VALUES (:firstname, :lastname, :email)");
$stmt->bindParam(':firstname', $firstname);
$stmt->bindParam(':lastname', $lastname);
$stmt->bindParam(':email', $email);
// insert a row
$firstname = "John";
$lastname = "Doe";
$email = "[email protected]";
$stmt->execute();
// insert another row
$firstname = "Mary";
$lastname = "Moe";
$email = "[email protected]";
$stmt->execute();
// insert another row
$firstname = "Julie";
$lastname = "Dooley";
$email = "[email protected]";
$stmt->execute();
echo "New records created successfully";
} catch(PDOException $e)
{
echo "Error: " . $e->getMessage();
}
$conn = null;
?>