SQL を書き始めると、「動けばよい」になりがちです。ところがユーザー入力をそのまま SQL に入れると、意図しないデータ取得や改ざんにつながることがあります。
この記事では、攻撃手法の暗記ではなく、初心者が最初に身につけるべきセキュアなSQLの基本5つを、理由とセットでまとめます。
この記事でわかること
- セキュアなSQLで最初に覚える5つの基本
- 「動くSQL」と「安全なSQL」の違い
- PHP / WordPress での具体的な書き方の方向性
- 安全そうに見える危ない書き方の見分け方
こんなときに読むと役立ちます
- SQL は書けるが、セキュリティの話になるといきなり難しく感じる
- 「SQLインジェクション」という言葉は聞いたが、何をすればよいか曖昧
- WordPress の
$wpdbを使うとき、どこまで任せていいかわからない - チュートリアル通りのコードを本番にそのまま使っていいか不安
まず押さえる:セキュアなSQLとは
ユーザー入力や外部データを「値」として安全に渡し、SQL の構造そのものは開発者が固定する書き方
ポイントは「入力を信用しない」ことです。フォームや URL、API の値は、すべて検証・加工の対象と考えます。
基本5つ:一覧
| # | 基本 | 一言で |
|---|---|---|
| 1 | ユーザー入力を SQL 文字列に直接入れない | 連結しない |
| 2 | プリペアドステートメントを使う | 値と SQL を分ける |
| 3 | DB ユーザーは必要最小限の権限だけ | 権限を絞る |
| 4 | エラーメッセージをそのまま画面に出さない | 情報を漏らさない |
| 5 | フレームワークの安全機能を使う | 自前実装を減らす |
1. ユーザー入力を SQL 文字列に直接入れない
いちばん多いのが、変数を "..." でくっつける書き方です。
// 危険:ユーザー入力 $email をそのまま SQL に連結
$sql = "SELECT * FROM users WHERE email = '" . $email . "'";
入力次第で、SQL の意味そのものが変わってしまいます。「動いたから大丈夫」はセキュアの根拠にならない、と覚えておいてください。
方針: SQL 文の形はコード側で固定し、変わるのは値だけにします。
2. プリペアドステートメントを使う
プリペアドステートメントは、SQL の枠組みと渡す値を分けて送る仕組みです。値は「データ」として扱われ、命令として解釈されにくくなります。
// PDO の例
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
「エスケープしたから大丈夫」だけに頼るより、プリペアドを第一選択にするのが安全です。
WordPress なら
$wpdb->prepare() を使います。プレースホルダは %s(文字列)、%d(整数)、%f(浮動小数)です。
$sql = $wpdb->prepare(
'SELECT * FROM {$wpdb->users} WHERE user_email = %s',
$email
);
$users = $wpdb->get_results( $sql );
3. DB ユーザーは必要最小限の権限だけ
アプリが使う DB ユーザーに DROP や GRANT など、不要な権限を付けないのもセキュアなSQLの一部です。
- 参照だけなら
SELECTのみ - 更新系は
INSERT/UPDATE/DELETEまでに絞る - 本番と開発でユーザーを分ける
たとえ SQL の書き方に不備があっても、被害の広がりを小さくする効果があります。
4. エラーメッセージをそのまま画面に出さない
DB のエラーには、テーブル名・カラム名・SQL の断片などが含まれることがあります。開発中はデバッグに便利ですが、本番でそのまま表示すると攻撃の手がかりになります。
- 本番ではユーザー向けに汎用的なメッセージだけ表示する
- 詳細はログに記録し、開発者だけが見られる場所に残す
- WordPress では
WP_DEBUG/WP_DEBUG_DISPLAYの設定に注意する
5. フレームワークの安全機能を使う
「自分でエスケープ関数を書く」より、検証済みの仕組みに任せる方が安全です。
| 環境 | 使うものの例 |
|---|---|
| WordPress | $wpdb->prepare()、WP_Query、サニタイズ関数群 |
| PHP 一般 | PDO のプリペアド、ORM のクエリビルダ |
| 生 SQL が必要なとき | プレースホルダ付きで書き、入力は必ずバインド |
ORM や高レベル API を使っていても、whereRaw() のように生 SQL を混ぜる箇所は要注意です。そこだけルールが崩れがちです。
安全そうに見える落とし穴
| 書き方 | なぜ足りないか |
|---|---|
intval() したから数値は安全 |
数値以外のパラメータや、別の SQL 部分は別問題 |
| クォートで囲んだ | エスケープ漏れ・エンコーディングの問題が残る |
| 管理画面だけだから | 権限のあるユーザー経由でもリスクはある |
| 社内ツールだから | 内部脅威・設定ミス・権限昇格は起こりうる |
コードを書くときのチェックリスト
- SQL 文字列に
.でユーザー入力を連結していないか - プリペアド(または
$wpdb->prepare())を使っているか - テーブル名・カラム名にユーザー入力を入れていないか(値と構造を混同していないか)
- 本番で DB エラーがそのまま表示されないか
- DB ユーザーの権限が必要以上に広くないか
新しい SQL を書いたら、「この入力はどこから来て、どのプレースホルダに渡しているか?」と1行ずつ確認する習慣が、いちばん効きます。
まとめ
- 1. ユーザー入力を SQL に直接連結しない
- 2. プリペアドステートメントで値を渡す
- 3. DB ユーザーは最小権限
- 4. 本番で DB エラーを見せない
- 5.
$wpdb->prepare()など、フレームワークの安全機能を使う
次に SQL を書くときは、まず「この値はプレースホルダ経由か?」と自分に問いかけてみてください。セキュアなSQLは、難しい攻撃対策より毎回の小さな習慣の積み重ねから始まります。
