【セキュリティ】プレースホルダでSQLインジェクションを防ぐ
前回までで、SQLインジェクションとOSコマンドインジェクションの実験を行いました。
今回は、プレースホルダという機能を使って、PHPコードをSQLインジェクションができない安全なコードにしていきたいと思います。
次のコードは、前回まで使用していたsectest.phpの内容です。
<?php
$servername = "localhost";
$username = "sectest"; // DB sectestにアクセスするユーザー
$password = "sectestpass";
$dbname = "sectest";
$conn = new mysqli($servername, $username, $password, $dbname);
// エラー確認
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
// 以下、変更が必要な箇所
$user = $_GET['username'];
$sql = "SELECT * FROM users WHERE username = '$user'";
$result = $conn->query($sql);
// ここまで
echo "<h2>クエリ: $sql</h2>";
if ($result && $result->num_rows > 0) {
echo "<h3>ユーザー情報:</h3><ul>";
while($row = $result->fetch_assoc()) {
echo "<li>ID: " . $row["id"] . ", USER: " . $row["username"] . ", PASS: " . $row["password"] . "</li>";
}
echo "</ul>";
} else {
echo "一致するユーザーは見つかりませんでした。";
}
$conn->close();
?>
問題があるのは下記のコードで、ユーザーが入力した値が$userに格納され、$userがそのままSQL文に埋め込まれる部分です。
$user = $_GET['username'];
$sql = "SELECT * FROM users WHERE username = '$user'";
$result = $conn->query($sql);
$user内にSQL文が含まれていると、ユーザーが入力したSQL文も実行される恐れがあります。
これを防ぐには、SQLの構文とデータ部分を分離するプレースホルダという機能を使用します。
// プレースホルダ付きSQL文を準備
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ?");
// 値をバインド(文字列型 's')
$stmt->bind_param("s", $_GET['username']);
// 実行
$stmt->execute();
// 結果取得
$result = $stmt->get_result();
・・・途中省略・・・
$stmt -> close();
$conn -> close();
?>
これにより、SQL構文は「SELECT * FROM users WHERE username = ?」と固定され、「bind_param("s", $_GET['username'])」から、?の部分に$_GET['username']の値が文字列として入ります。
したがって、$_GET['username']の中にSQL文が含まれていても、単なる文字列として処理されます。
"s"は文字列ですが、他にも"i":integer(整数)、"d":double(実数、小数)、"b":blob(バイナリデータ)なども指定できます。
例えば、
$stmt = $conn->prepare("SELECT * FROM products WHERE id = ? AND category = ?");
$stmt->bind_param("is", $id, $category);
とすれば、最初の?には整数型の$idが、2番めの?には文字列型の$categoryが入ります。
ちなみに、$stmtの「stmt」は(SQL) Statementの略です。
実行結果
それでは、正しく処理できるか確認してみましょう。
まずは、普通にDBに登録されているユーザー名を入れて検索してみます。


正しく表示されています。
次に、SQLインジェクションを試みます。


プレースホルダーを導入したことにより、「1' or '1'=1」という文字列のユーザーを検索するため、一致するユーザーは見つからないと表示されます。
実際に手を動かしてみると、理解が深まります。
そろそろ、春期の情報処理試験ですね。
寒暖差が激しいので、体調に気をつけていきましょう。