【セキュリティ】プレースホルダでSQLインジェクションを防ぐ

【セキュリティ】プレースホルダでSQLインジェクションを防ぐ
Photo by FlyD / Unsplash

前回までで、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」という文字列のユーザーを検索するため、一致するユーザーは見つからないと表示されます。

実際に手を動かしてみると、理解が深まります。


そろそろ、春期の情報処理試験ですね。
寒暖差が激しいので、体調に気をつけていきましょう。