概要
ch04 以降ずっと「値はプレースホルダで渡す」と繰り返してきた理由を、この章で実弾を撃って確認する。SQL インジェクション とは、ユーザー入力に SQL の構文要素 (' や ; や --) を紛れ込ませて、開発者の意図しない SQL を DB に実行させる 攻撃で、Web アプリの最古典の脆弱性。情報漏洩・改ざん・テーブル破壊と被害幅も広い。
仕組みは単純で、"WHERE name = '$name'" のように 文字列連結 で SQL を組み立てていると、入力 ' OR '1'='1 を埋めた瞬間に条件が常に真になり 全件返ってくる。'; DROP TABLE users; -- を紛れ込ませれば、テーブルを消されることすらある。
これに対するプレースホルダ (prepare + execute) は、SQL の 形 と 値 を 別ルート で DB に送る方式。DB は最初に「形」をパースして実行計画を作り、後から来た「値」は 常にデータとして扱う。だから ' も ; も SQL 構文として解釈されない。ドリルは危険な連結版 starter をプレースホルダ版に書き直して、両者の違いを手で覚える。
学習目標
- 文字列連結で SQL を組み立てる危険性を体感する
- プレースホルダ (
?) +prepare/executeがなぜ安全なのか、その理由を言える - ユーザー入力に特殊文字 (
'や;) が混ざっても壊れない書き方が分かる
ドリル
| no | 内容 |
|---|---|
| 01 | 文字列連結で書かれた starter を、プレースホルダ版に直す (' 入りの名前で動作確認) |
| 02 | 検索フォーム想定。LIKE 検索でもプレースホルダを使う |
採点用 DB について
採点ランナーは tests/setup.sql を一時 SQLite に流し込み、そのファイルパスを環境変数 DOJO_DB_PATH で渡してきます。受講生コードは次の形で接続してください。
$pdo = new PDO('sqlite:' . getenv('DOJO_DB_PATH'));
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);なぜ文字列連結が危険か
// ❌ 危険: 入力をそのまま SQL に連結
$name = $_GET['name'];
$sql = "SELECT * FROM users WHERE name = '$name'";ユーザーが name=' OR '1'='1 を入れると、SQL は
SELECT * FROM users WHERE name = '' OR '1'='1'
になり、全件返ってくる。さらに悪意ある入力で '; DROP TABLE users; -- を渡されると、テーブル自体を壊される可能性がある。
プレースホルダ版なら、入力は 値 として扱われるので SQL 構文として解釈されない。
📝 演習: ドリル 01 — 連結をプレースホルダに直す
問題
starter.php はわざと 文字列連結 で SQL を組み立てている。これだと ' (シングルクォート) を含む名前を入れた瞬間に SQL 構文エラーで落ちる。
ターゲット名:
O'Brien
users テーブルに O'Brien (id=1) が登録されている。標準入力からこの名前を受け取り、
1: O'Brien
と出力するように プレースホルダ + prepare/execute で書き直してほしい。
期待される出力
1: O'Brienヒント
$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: 山本次郎ヒント
WHERE name LIKE ?でプレースホルダ- 値の側で
'%' . $keyword . '%'を作って渡す - SQL の中に
'%$keyword%'のように埋め込むのは NG ORDER BY idで並び順を固定