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_ERRMODEをPDO::ERRMODE_EXCEPTIONにする習慣を付けておく
ドリル 02 — LIKE 検索もプレースホルダで
問題
ユーザーが入力したキーワードで users.name を 部分一致検索 (LIKE) するコードを書きたい。
- 標準入力からキーワードを 1 行受け取る
nameにそのキーワードを含むユーザーをid昇順で全件出力する%(ワイルドカード) は PHP 側 で付け、プレースホルダには値だけ渡す
users テーブルには次が入っている (setup.sql 参照):
| id | name |
|---|---|
| 1 | 山田太郎 |
| 2 | 鈴木花子 |
| 3 | 山本次郎 |
| 4 | O'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で並び順を固定