はじめに
自作CMSを作る過程で、SQLインジェクションへの対策としてバインドパラメーターを用いました。
SQLインジェクションとは何か、どのような対策を施したかをまとめておきます。2025-04-10
自作CMSを作る過程で、SQLインジェクションへの対策としてバインドパラメーターを用いました。
SQLインジェクションとは何か、どのような対策を施したかをまとめておきます。言語
参考リンク
悪意のある入力をSQL文として認識させ、データベースのテーブルを削除したり情報流出をさせる攻撃方法のことです。
今回は、ユーザーネームとパスワードが必要なログイン画面を例として説明します。このようなユーザー名とパスワードを入力するログイン画面があったとします。
このとき、以下のようなSQL文が実行されているとします。SELECT * FROM users WHERE username = '$username' AND password = '$password';
例えば、 username = ‘admin’ password = ‘password111’ のときに実行されるSQL文は
SELECT * FROM users WHERE username = 'admin' AND password = 'password111';
これがデータベースに存在していればログインが成功し、存在していなければログイン失敗になります。
この設定下で、ユーザーがこのように入力したとします。
' OR '1'='1
このときに実行されるSQL文は
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '';
ここが少し難しいところです。
元のSQL文は、ユーザーが入力された文字列に ‘ ‘ をつけてSQL文の値として実行します。 今回であれば、「' OR '1'='1」に ' ' をつけるので、結果として「' ' OR '1'='1’」というusernameが入力された扱いになります。 パスワードは空なので ' ' になります。 この説明は上の画像を見てもらうと分かりやすいと思います。
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '';
このSQL文の評価を見てみましょう。 SQL文では、ANDはORよりも優先度が高くなります。 つまり、
SELECT * FROM users WHERE (username = '') OR ('1'='1' AND password = '');
かっこをつけるとこのような見た目になります。
username = ' ' この部分は空のユーザー名が存在しないので FALSE になります。 '1' = '1' これは等しいので TRUE になります。 password = ' ' これはパスワードが間違っているので FALSE になります。 後半部分は TRUE AND FALSE なので、結果は FALSE 全て合わせると FALSE OR FALSE になるのでログインできないですね!
・・・ 違います!! これは間違っています!!
よく思い出してください、ORを入れたのはユーザー名の入力欄ですよね? ということは、ORの影響範囲はユーザー名の条件だけです。 そしてANDは、ユーザー名とパスワードにかかっていたので、ユーザー名にのみ影響します。 つまり、
username = ' ' この部分は空のユーザー名が存在しないので FALSE になります。 '1' = '1' これは等しいので TRUE になります。 この2つにのみORは影響するので、FLASE OR TRUE で TRUEになります。 password = ' ' これはユーザー名にのみ影響を受けて評価されますが、ユーザー名は今回 FALSE なので無視されます。 結果として全体で TRUE になり、ログインできてしまうというわけです。
今回のように不正ログインされてしまう理由は、ユーザーの入力した値をそのままSQL文に組み込んでいることが原因です。 その対策として、バインドパラメータがあります。 バインドパラメーターはユーザーの入力をSQL文として実行するのではなく、分離したデータとして扱う方法です。 この分離したデータのことをプレースホルダーと言います。
// :username とすると、ユーザー名として入力した値をここに埋め込む変数といった意味になる
// :password も同様にパスワードとして入力した値をここに埋め込む変数といった意味になる
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
// 埋め込み方を指定する
// :usernameには変数$usernameを紐付け、string型の文字列だと教える
$stmt->bindValue(':username', $username, PDO::PARAM_STR);
// :passwordには変数$passwordを紐付け、string型の文字列だと教える
$stmt->bindValue(':password', $password, PDO::PARAM_STR);
// 実行する
$stmt->execute();
プレースホルダーはbindValueを使って、紐付ける変数名と型を指定します。
このような方法で記述すると、SQLインジェクション攻撃を防ぐことができます!
SQL文を実際に埋め込むことは止めましょう。実際に使っているログイン画面の処理部分はセキュリティの観点から載せませんが、バインドパラメーターを用いている部分を紹介します。
記事編集画面でサムネイル画像を新しい物に更新する部分です。
SQL文の中身は全てプレースホルダーを使い、bindValueを使って紐付けています。// サムネイルパスを更新
public function updateThumbnailPath($articleId, $newFileName, $newFilePath)
{
try {
$stmt = $this->db->prepare("UPDATE " . $this->config->get('tables')['thumbnails'] . " SET file_name = :file_name, file_path = :file_path WHERE article_id = :article_id");
$stmt->bindValue(":file_name", $newFileName, PDO::PARAM_STR);
$stmt->bindValue(":file_path", $newFilePath, PDO::PARAM_STR);
$stmt->bindValue(":article_id", $articleId, PDO::PARAM_INT);
return $stmt->execute();
} catch (PDOException $e) {
echo "サムネイルパスの更新に失敗しました: " . $e->getMessage();
return false;
}
}
SQLインジェクションの説明とその対策方法についてまとめました。
攻撃者がユーザー名に SQL文を入力した場合の評価部分が難しかったと思います。
この攻撃によって本来見ることができないデータを盗み見られたり流出する恐れがあります。
しっかり理解しておきましょう!