1. クライアント・サーバモデル #client-server
Web サイトを開くとき、ブラウザ(クライアント)は HTTP というプロトコルでサーバに「このURLのページをください」とリクエストを送ります。サーバはそのリクエストを処理して、HTML を返します。
SQL injection が問題になるのは、サーバが内部でデータベースと話している場合です。ブラウザから受け取ったユーザ入力を使ってサーバが SQL 文を組み立て、その SQL を DB に送って結果を取得し、ページに埋め込んで返す——この流れの中の「SQL 文を組み立てる」段階が攻撃ポイントになります。
ブラウザ ──(HTTP)──> サーバ ──(SQL)──> DB
<── 結果 ──
<── HTML ────
2. URL のクエリパラメータ #query-string
? 以降がサーバへの引数。
URL に ?key=value&key2=value2 の形でついている部分を「クエリパラメータ(クエリ文字列)」と呼びます。サーバはこの値を取り出してプログラムの中で使います。
https://utsuroi.example.com/products?category=スキンケア
───────── ────────
パラメータ名 値
Lab 01 では category というパラメータの値が、サーバ側で SQL の WHERE category = '...' の ... 部分にそのまま使われています。ここが攻撃ポイント。
3. HTTP Cookie #cookie
Cookie は、サーバが「次にアクセスしたときも持ってきてね」とブラウザに渡す小さなキー値ペアです。ブラウザはそれを保存し、同じサーバへの毎回のリクエストで送信します。
Lab 11〜17 では追跡用 Cookie(TrackingId=usr_xxx)が使われています。Cookie の値もサーバ側で SQL クエリに使われており、URL のパラメータと同じく「ユーザが操作できる入力値」=攻撃ポイントになります。「URL ではなく Cookie だから安全」とは言えないのがポイント。
4. フォーム送信(GET/POST) #form
HTML の <form> 要素は、ユーザの入力値をサーバに送る仕組み。GET と POST の2種類があります。
- GET — 値を URL のクエリパラメータに付けて送る。検索ボックスなど
- POST — 値をリクエストの本文(body)に入れて送る。ログインフォーム、データ更新など
Lab 02 のログインフォームは POST、Lab 18 の XML POST は POST のリクエストボディに XML を入れて送る形。どちらの方式でも、ユーザが値を操作できる点は同じで、SQLi の攻撃面になります。
5. テーブル・行・カラム #table
リレーショナルデータベース(MySQL、PostgreSQL、Oracle、MS SQL Server など)では、データはテーブル(表)として保存されます。テーブルはカラム(列)と行から成り、エクセルのシートに近い構造です。
例: products テーブル
┌────┬──────────────────────┬────────────┬────────┐ │ id │ name │ category │ price │ ├────┼──────────────────────┼────────────┼────────┤ │ 1 │ 化粧水「凪」 │ スキンケア │ 3800 │ │ 2 │ ナイトクリーム │ スキンケア │ 5200 │ │ 3 │ マットリップ「朱」 │ メイクアップ │ 3200 │ └────┴──────────────────────┴────────────┴────────┘
1つの DB には複数のテーブル(products、members、orders など)があり、各テーブルが独自のカラム構成を持ちます。
6. SELECT と WHERE #select-where
SQL でデータを取り出す基本構文:
SELECT name, price -- 取り出すカラム FROM products -- どのテーブルから WHERE category = 'スキンケア' -- 絞り込み条件
上のクエリは products テーブルから「category が "スキンケア" の行」だけを取り出し、name と price の2カラムを返します。
SELECT * と書けば全カラム取得。WHERE を省略すれば全行取得。複数条件は AND や OR で結合します:
SELECT * FROM members WHERE member_id = 'admin' AND password = 'secret'
Lab 02 のログインバイパスは、まさにこの2条件のうち後者を消す攻撃。
7. 文字列リテラルとシングルクォート #string-literal
'。
SQL の中で文字列(テキスト)はシングルクォート(')で囲んで書きます。これを文字列リテラルと呼びます。
SELECT * FROM products WHERE category = 'スキンケア' ↑ ↑ ここで開いて ここで閉じる
数値の場合はクォートで囲みません(WHERE id = 5)。文字列だけがクォートで囲まれます。
もし、ユーザが入力した値がそのまま'...'の中に入る実装になっていて、ユーザが入力に ' を含めたら——シングルクォートが「文字列を閉じる印」として解釈されてしまい、その後ろに任意の SQL を書けることになります。これがインジェクションの第一原理です。
8. エスケープという概念 #escape
本来、' を含む文字列を SQL で正しく扱うには「エスケープ」という処理が必要です。多くの DB では ' を ''(2つ続ける)か \' で表現します:
-- John's Coffee という店名を扱いたい SELECT * FROM shops WHERE name = 'John''s Coffee' ──── 2つの ' で「シングルクォート1個」を表す
アプリケーション開発では、ユーザ入力に含まれる ' を SQL に渡す前にエスケープする必要があります。これを忘れる、あるいは不完全なエスケープ実装になっていると、SQL インジェクションの脆弱性が生まれます。
実は現代の Web 開発では、エスケープを手動で行うのではなくプレースホルダ(プリペアードステートメント)という仕組みを使うのが推奨されます。プレースホルダは「SQL の構造」と「値」を分離する仕組みで、原理的に SQLi を不可能にします。詳しくはLab 01 のサイト側の対策セクションで。