章: 第6章: セキュリティと本番運用
ユーザーの入力値をそのままSQLに埋め込んでいませんか?
"SELECT * FROM users WHERE email = '" . $email . "'" のようにPHPで文字列連結してSQLを組み立てると、' OR '1'='1 といった入力を受けたとき全データが返る脆弱性になります。これがSQLインジェクション攻撃で、OWASP Top 10に毎回ランクインする重大リスクです。
根本的な対策はただ一つ:プリペアドステートメント(プレースホルダ)を使い、SQL文と値を分離して渡すことです。
危険な書き方と安全な書き方の比較
| 方法 | リスク | 推奨度 |
| 文字列連結でSQL生成 | SQLインジェクション攻撃に脆弱 | 使用禁止 |
addslashes などでエスケープ |
抜け穴が存在し不完全 | 非推奨 |
| プリペアドステートメント(PDO) | SQL文と値が完全分離され安全 | 推奨 |
| ORMのクエリビルダ経由 | 内部でプレースホルダを使用 | 推奨 |
| 生のクエリをORM経由でrawSQL | 文字列連結と同様のリスクあり | 要注意 |
チェックポイント: ORMを使っていても
whereRaw()やDB::statement()に変数を文字列連結で渡すとSQLインジェクションが発生します。rawメソッドには必ずバインディング引数を使ってください。
実際のコードサンプル
-- NG: 文字列連結でSQL生成
-- SELECT * FROM users WHERE email = '" + email + "';
-- OK: プレースホルダ
PREPARE stmt FROM 'SELECT * FROM users WHERE email = ?';
SET @email = 'yuki@example.com';
EXECUTE stmt USING @email;
DEALLOCATE PREPARE stmt;
まとめ & 次のステップ
- SQL文とユーザー入力値は必ず分離し、プレースホルダ経由でバインドする
- PHPでは PDO のプリペアドステートメントを使い、文字列連結によるSQL生成を完全に禁止する
- ORMを使う場合も
whereRaw()などの生SQLには必ずバインディング引数を渡す - コードレビューで「変数をSQLに直接埋め込んでいる箇所がないか」を確認するルールを設ける
- WAF(Webアプリケーションファイアウォール)は補完策であり、プレースホルダの代替にはならない
次回は スロークエリログの活用 を学びます。勘に頼らず計測ベースでクエリを改善する方法を解説します。