セキュリティ●●●○○

SQLインジェクション

入力文字列にSQLを紛れ込ませ、意図しないクエリを実行させる攻撃。

概要

SQLインジェクションは、アプリがユーザー入力を使って SQL 文を組み立てる際に、入力の中へSQLの断片を紛れ込ませ、開発者が意図しないクエリをデータベースに実行させる攻撃です。ログインフォームの認証回避、他人のデータの読み出し、テーブルの改ざんや全削除まで、被害はデータベースにできることすべてに及びます。

たとえば「ユーザー名とパスワードが一致するか」を確かめるクエリに、パスワード欄へ ' OR '1'='1 のような文字列を送り込むと、条件式が常に真になり、パスワードを知らなくてもログインが通ってしまう — これが最も有名な例です。1990年代から知られる古典的な攻撃でありながら、いまなお大規模な情報漏えい事件の主要な原因であり続けています。

なぜ生まれたか

この脆弱性は、SQL というクエリ言語が「命令(構文)」と「データ(値)」を同じ一本の文字列の中に混在させて表現する、という性質から生まれます。SELECT * FROM users WHERE name = '入力値' というクエリでは、SELECTWHERE は命令で、'入力値' の部分はデータです。ところが、この文字列をアプリ側で単純な連結によって組み立てると、データであるはずの入力の中に ' やキーワードを混ぜられたとき、データベースはそれを命令の一部として解釈してしまいます。

初期のアプリ開発では、入力値をそのまま文字列連結でSQLに埋め込むのが一般的でした。「フォームの値を WHERE 句にはめ込むだけ」という素朴な実装が、そのまま攻撃の入り口になったのです。これは XSS が「HTMLの中でデータとコードの境界が崩れる」問題であるのと、まったく同じ構図です。どちらも「外部からの入力を、うっかり実行対象のコードとして解釈させてしまう」という共通の根を持ち、だからこそ対策の思想も共通しています。

詳細

攻撃の流れ

認証回避を例に、ステップで見てみます。アプリが SELECT * FROM users WHERE name = '<入力>' AND password = '<入力>' というSQLを文字列連結で作っているとします。

正常な入力と攻撃入力が、同じ連結処理を通ってまったく違うクエリになる様子を対比してみます。

正常な入力pass123攻撃者の入力’ OR ‘1’=‘1アプリは同じ文字列連結で SQL を組み立てるWHERE password = ‘pass123’入力全体がただの値として扱われるWHERE password = ” OR ‘1’=‘1’引用符が文字列を閉じ、OR 以降が構文として解釈される意図どおりの一致判定条件が常に真になり認証を通過
同じ文字列連結でも、入力にSQLの断片が混ざると構文そのものが変わってしまう

'1'='1' は常に真なので、OR でつながれた条件全体が真になり、パスワードの一致判定が無効化されます。同じ発想を推し進めると、UNION SELECT で別テーブルの内容を検索結果に混ぜて盗み出したり、; で文を区切って DROP TABLE のような破壊的な命令を続けたりと、攻撃は認証回避にとどまりません。さらに、エラーメッセージの差異や応答時間の違いから一文字ずつ情報を推測する「ブラインドSQLインジェクション」という手法もあり、画面に結果が出ない場合でもデータを抜き出されうる点が厄介です。

対策の本命: プレースホルダ(パラメータ化クエリ)

唯一にして本質的な対策は、プレースホルダ(パラメータ化クエリ、バインド変数)を使うことです。WHERE name = ? のようにSQL文の骨組みを先に確定させ、値は別の経路でデータベースに渡します。こうすると、データベースは「構文はこれ、値はこれ」と明確に区別して受け取るため、入力に何が含まれていても、それは常に「ただの値」として扱われます。' OR '1'='1 を渡しても、「そういう名前のユーザーを探す」という検索になるだけで、構文として解釈されることはありません。

ここで重要なのは、この対策が「危険な文字を潰す」のではなく「データと構文の経路を最初から分離する」アプローチだという点です。入力をエスケープ(' を無害化するなど)する方法もありますが、文字コードや方言の差異で漏れが生じやすく、本命にはなりません。境界を後から守るのではなく、そもそも混ざらない仕組みにするのがプレースホルダの思想です。

同じ攻撃文字列が、文字列連結では構文に化け、プレースホルダではただの値に留まる様子を、クエリ組み立てシミュレータで見比べてみてください。

⚡ 体験: 入力がSQLに化ける瞬間を見る
組み立てられるSQL(入力がそのまま文に混ざる)SELECT * FROM users WHERE name = 'alice' AND password = ''

判定: 該当ユーザーなし → ログイン失敗

文字列連結では、入力に含まれる ' が文字列を閉じ、続く部分がSQLの構文として解釈されます(朱色の部分)。データのつもりが命令になる — これがSQLインジェクションです。

ORMと実務上の監査ポイント

現代のアプリは ORM(オブジェクトリレーショナルマッパー)を通してデータベースを扱うことが多く、ORMが発行するクエリは内部でプレースホルダを使うため、通常は自動的に守られます。「ORMを使っているから安全」と言われるのはこのためです。

ただし油断は禁物です。ORMを使っていても、複雑な集計や動的な条件のために「生SQL」を書く箇所は残りがちで、そこで文字列連結をしてしまえば穴が空きます。また、テーブル名やカラム名、ORDER BY の並び順のようにプレースホルダで渡せない部分を動的に組み立てる場合は、許可リスト(ホワイトリスト)方式で入力を検証する必要があります。監査では「生SQLを書いている箇所」と「識別子を動的に組み立てている箇所」を洗い出すのが要点です。

多層防御

土台をプレースホルダで固めたうえで、被害を抑える備えを重ねます。アプリが使うデータベースアカウントの権限を必要最小限に絞れば(たとえば読み取り専用の画面には SELECT 権限しか与えない)、万一注入されても被害範囲を限定できます。入力値の型や長さの検証、エラーメッセージにデータベースの内部情報を出さないこと、WAF(Web Application Firewall)による既知パターンの遮断なども補助になります。とはいえ、これらはあくまで補助であり、根本対策はあくまでパラメータ化クエリの徹底である、という優先順位を取り違えないことが肝心です。