初級では「データのはずの入力が命令になってしまう」本質を見ました。中級では、なぜ混ざるのかと、どの層で断つかを整理します(守り全体は姉妹ユニット Webのセキュリティ(全体像))。
概要 — まず全体をつかむ
詳細 — 1段階ずつ追う
どう起きる?
原因は一点です。入力を文字列連結で SQL に混ぜると、入力の中身が「命令の一部」として解釈されてしまう。
DB にとって届いた SQL は一続きの文字列です。命令の骨組みとデータの境目は、組み立てた側の意図でしかありません。連結でつなぐと、入力に混ぜた命令の切れ端が骨組みを書き換え、全件を抜く・消す・ログインを突破する、といったことが起きます。
アニメーション『SQLインジェクション(命令とデータの分離)』を開く
登場人物メモ:
- ※1 命令とデータの分離 — SQL文の骨組みと、入力値を別々にDBへ渡す考え方
- ※2 最小権限 — アプリのDBユーザーに必要以上の権限を与えないこと
やさしく言うと(初級)
DBへの命令(SQL)を、入力をそのまま文字列でつなげて作ると危険です。
たとえば「名前で検索」のとき、入力欄に普通の名前ではなく命令の切れ端を入れられると、それが命令の一部として解釈され、全件を抜き出す・消す・ログインを突破する、といったことが起きます。
アニメーション『SQLインジェクション(命令とデータの分離)』を開く
どう防ぐ?
- パラメータ化クエリ(プレースホルダ) — 命令の骨組みを先に固定し、入力は「値(データ)」として別枠で渡す。命令とデータが分離されるので入力は命令として動かない。これが王道
- 文字列連結で SQL を組み立てない — これが鉄則。自前のエスケープは抜けやすい
- ORMの適切な利用 — ORM も内部でパラメータ化を使うが、生SQLを組み立てる箇所は同じリスクなので油断しない
- DBユーザーの最小権限 — アプリ用ユーザーに削除・全件取得などの過剰な権限を与えない。破られても被害を絞る
- 識別子は許可リストで — 表名・列名・並び順(ORDER BY)はプレースホルダで「値」として渡せない。動的に変えるなら、固定の許可リストから選ぶ(入力をそのまま使わない)
- エラー詳細を返さない — DBのエラーをそのまま画面に出すと、テーブル構造などのヒントを与える
- 入力検証 — 補助として型や形式も確かめる
この部分をもっと深く(上級)
「パラメータ化すれば安全」——なぜそう言えるのか。鍵は、DBとのやり取りが2段階に分かれている点です。
- 準備(prepare) — アプリは
SELECT * FROM users WHERE name = ?のような骨組みだけをDBに送る。DBはこの時点で構文を解析し、構文木(命令の形)を確定する。?は「ここに後で値が入る」という穴として記憶される - 実行(execute) — 続いて、入力値を別のメッセージとして送る。値は確定済みの穴にバインドされるだけで、構文の再解析は起きない
- だから入力に
' OR '1'='1のような命令の切れ端が混じっていても、それは穴に収まる1個の文字列としてしか扱われない。骨組みはもう変えられない ※1
アニメーション『SQLインジェクション(命令とデータの分離)』を開く
文字列連結は、この2段階を1段階に潰してしまいます。骨組みと値を先に文字列として合体させ、その完成品をDBに構文解析させるので、値の中の記号が構文として解釈される余地が残る。パラメータ化の本質は「構文の確定を、値が届く前に済ませる」ことです。
登場人物メモ:
- ※1 バインド — 確定済みの命令の「穴」に、入力値をデータとして流し込む操作。値は命令として再解釈されない
- ※2 識別子 — 表名・列名・別名など、命令の骨組みを構成する名前。リテラル(値)とは別扱い
アニメーション『パラメータ化の内部(連結 vs プリペアド)』を開く
やさしく言うと(初級)
- パラメータ化クエリ(プレースホルダ) — 命令の骨組みを先に固定し、入力は「値(データ)」として別枠で渡す。これなら入力は命令として動かない
- 文字列連結でSQLを組み立てない — これが鉄則
- 最小権限 — アプリのDBユーザーに、必要以上の権限(削除・全件)を与えない
- 入力検証 — 補助として、型や形式も確かめる
⚠️ 気をつける
- エスケープ自作に頼る — 自前の文字置換は抜け穴が残りやすい。パラメータ化が正解
- エラーメッセージの露出 — 詳細を隠しても、応答の真偽や時間差から探るブラインドな手口がある。だからエラー非表示は補助
- ORMでも油断しない — 生SQLや動的な条件組み立ての箇所は同じリスク
- 種類を知る — 結果に差が出るエラーベース、差が見えにくいブラインド(真偽・時間差)など手口は複数あるが、対策の根本は同じ
この部分をもっと深く(上級)
- WAFは万能ではない — WAF(Web Application Firewall)は既知の攻撃パターンを弾くが、パターンマッチングが土台なので、コメント挿入・大文字小文字・エンコード違い・等価な別表現で回避されうる。入口の網であって、根治ではない
- 多層防御の一枚として — WAFは「時間稼ぎと検知」に価値がある。ただしアプリ側のパラメータ化が本丸で、WAFに寄りかかって連結を放置するのは順序が逆
- エラーの露出 — 詳細エラーを隠してもブラインドで抜かれる。非表示は補助と心得る
- 入力検証との役割分担 — 入力の検証 は「形」を入口で確かめる担当。SQLインジェクションの根治は出口(DBへ渡す所)のパラメータ化で、両者は別concernとして両方やる
やさしく言うと(初級)
- エスケープ自作に頼る — 自前の文字置換は抜けやすい。パラメータ化が正解
- エラーメッセージの露出 — DBのエラーをそのまま返すと、構造のヒントを与えてしまう
- ORMでも油断しない — 生SQLを組み立てる箇所は同じリスク
理解度チェック
そのまま解けます(成績は保存されません)。無料アカウントを作ると、学習の記録と進捗の山登りが始まります。
問1. ブラインドSQLインジェクションの説明として正しいのは?
問2. パラメータ化クエリ(プレースホルダ)が有効なのはなぜか?
問3. 入力が「命令」として解釈されてしまう根本原因はどれ?
問4. 表名・列名や並び順(ORDER BY)を入力に応じて動的に変えたい。安全な方法はどれ?
問5. 自前のエスケープ(危険な記号の文字置換)に頼るのが良くない理由は?
問6. 「DBの詳細エラーを画面に出さない」対策の位置づけとして正しいのは?
問7. 表名・列名や並び順を動的に変えるとき 入力を直接使わず あらかじめ用意した安全な選択肢から選ぶ方式を「○○リスト」と呼ぶ。○○に入る語(漢字2文字)は?
問8. アプリ用のDBユーザーに 必要以上の権限を与えないことを漢字4文字で何という?