採点されません。匿名です。 間違えることは学習の一部です。読むだけでもかまいません。

SQL インジェクション / プリペアド vs 連結

テクノロジ系 / db (PDO / SQL / マルチDB)

SQL インジェクション / プリペアド vs 連結

Lesson 11 / Chapter 9

文字列連結で SQL を組み立てると壊れる

<?php
$name = $_GET['name'];                              // ユーザー入力
$sql  = "SELECT * FROM users WHERE name = '$name'"; // ❌ そのまま連結
$pdo->query($sql);

入力が普通の名前ならよさそうに見える。

問題は、入力が ' OR '1'='1 のとき:

SELECT * FROM users WHERE name = '' OR '1'='1'

全件返ってくる。ログイン処理でこれをやると突破される。

さらに悪意のある入力

入力が '; DROP TABLE users; -- だったら:

SELECT * FROM users WHERE name = ''; DROP TABLE users; --'
  • ; で SQL 文を区切られ、DROP TABLE users続けて実行される
  • -- 以降はコメント扱いで無効化される

これが SQL インジェクション。データ全削除やパスワード抜き取りに使われる典型攻撃。

プレースホルダで防ぐ

<?php
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = ?");
$stmt->execute([$name]);
  • ? の部分は 値の入る場所 として SQL に渡される
  • $name として扱われ、SQL 構文として解釈されない
  • 入力に '; が混じっていても、ただの文字として比較される

' OR '1'='1 という名前のユーザーを探しに行くだけ (誰もいないので 0 件)。

prepare / execute の流れ

<?php
// 1. SQL の「型」を DB に登録 (値の場所だけ ? にする)
$stmt = $pdo->prepare("SELECT id, name FROM users WHERE name = ?");

// 2. 値を渡して実行
$stmt->execute([$name]);

// 3. 結果を取り出す
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
    echo "{$row['id']}: {$row['name']}\n";
}

ポイント: prepare した時点で SQL の構造は確定済み。後から値を渡しても 構造を書き換えられない

LIKE 検索でも同じルール

部分一致検索 (name LIKE '%山%') でも、% はクエリ側で作って、入力は値として渡す。

<?php
$keyword = trim(fgets(STDIN));
$stmt = $pdo->prepare("SELECT id, name FROM users WHERE name LIKE ?");
$stmt->execute(['%' . $keyword . '%']);  // % は PHP 側で付ける
  • 連結は SQL の中ではなく、プレースホルダに渡す値の側 で行う
  • これなら keyword'; が入っても安全

このチャプターでできるようになること

✅ 文字列連結が危険な理由を ' OR '1'='1 の例で説明できる

prepare + ? で SQL の構造と値を分離する書き方ができる

✅ LIKE 検索でも、% は SQL 側に書いて値だけプレースホルダに渡せる

✅ 「ユーザー入力を SQL に連結したら危険」という反射を持てる

→ ドリルへ

考えてみよう

既定では正解・不正解の判定はしません。自分のペースで「答えを見る」を。採点してほしい場合だけ「採点してほしい」を押してください。

ドリル 01 — 連結をプレースホルダに直す

問題

starter.php はわざと 文字列連結 で SQL を組み立てている。これだと ' (シングルクォート) を含む名前を入れた瞬間に SQL 構文エラーで落ちる。

ターゲット名:

O'Brien

users テーブルに O'Brien (id=1) が登録されている。標準入力からこの名前を受け取り、

1: O'Brien

と出力するように プレースホルダ + prepare/execute で書き直してほしい。

期待される出力

1: O'Brien

採点

php scripts/grade.php topics/db/ch09-sql-injection/drill/01-bad-concat/

ヒント

  • $pdo->prepare("SELECT id, name FROM users WHERE name = ?")? がプレースホルダ
  • $stmt->execute([$name]) で値を渡す
  • 連結 ("... WHERE name = '$name'") は禁止
  • PDO::ATTR_ERRMODEPDO::ERRMODE_EXCEPTION にする習慣を付けておく

ドリル 02 — LIKE 検索もプレースホルダで

問題

ユーザーが入力したキーワードで users.name部分一致検索 (LIKE) するコードを書きたい。

  • 標準入力からキーワードを 1 行受け取る
  • name にそのキーワードを含むユーザーを id 昇順で全件出力する
  • % (ワイルドカード) は PHP 側 で付け、プレースホルダには値だけ渡す

users テーブルには次が入っている (setup.sql 参照):

idname
1山田太郎
2鈴木花子
3山本次郎
4O'Brien

期待される入力

期待される出力

1: 山田太郎
3: 山本次郎

採点

php scripts/grade.php topics/db/ch09-sql-injection/drill/02-search-safe/

ヒント

  • WHERE name LIKE ? でプレースホルダ
  • 値の側で '%' . $keyword . '%' を作って渡す
  • SQL の中に '%$keyword%' のように埋め込むのは NG
  • ORDER BY id で並び順を固定