utsuroi sqli labs 知識ベース 01 / 05 — 基礎編
Foundations · Knowledge Base
01

基礎編 — Web と SQL の前提

Web requests, databases, and the SQL string literal

SQL injection を理解するには、まず「ブラウザがどう DB と話しているか」と「SQL 文の中で文字列がどう扱われているか」を押さえる必要があります。ここがあやふやだと、攻撃の核となる「文字列リテラルを途中で閉じる」発想が腑に落ちません。

1. クライアント・サーバモデル #client-server

ブラウザは「お願い」、サーバは「返答」。

Web サイトを開くとき、ブラウザ(クライアント)は HTTP というプロトコルでサーバに「このURLのページをください」とリクエストを送ります。サーバはそのリクエストを処理して、HTML を返します。

SQL injection が問題になるのは、サーバが内部でデータベースと話している場合です。ブラウザから受け取ったユーザ入力を使ってサーバが SQL 文を組み立て、その SQL を DB に送って結果を取得し、ページに埋め込んで返す——この流れの中の「SQL 文を組み立てる」段階が攻撃ポイントになります。

ブラウザ ──(HTTP)──> サーバ ──(SQL)──> DB
                              <── 結果 ──
       <── HTML ────

2. URL のクエリパラメータ #query-string

URL の ? 以降がサーバへの引数。

URL に ?key=value&key2=value2 の形でついている部分を「クエリパラメータ(クエリ文字列)」と呼びます。サーバはこの値を取り出してプログラムの中で使います。

https://utsuroi.example.com/products?category=スキンケア
                                       ─────────  ────────
                                       パラメータ名   値

Lab 01 では category というパラメータの値が、サーバ側で SQL の WHERE category = '...'... 部分にそのまま使われています。ここが攻撃ポイント。

4. フォーム送信(GET/POST) #form

入力欄から値をサーバに渡す方法は2通り。

HTML の <form> 要素は、ユーザの入力値をサーバに送る仕組み。GET と POST の2種類があります。

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 には複数のテーブル(productsmembersorders など)があり、各テーブルが独自のカラム構成を持ちます。

6. SELECT と WHERE #select-where

「どのカラムを」「どの条件で」取り出すか。

SQL でデータを取り出す基本構文:

SELECT name, price -- 取り出すカラム
FROM products       -- どのテーブルから
WHERE category = 'スキンケア'  -- 絞り込み条件

上のクエリは products テーブルから「category が "スキンケア" の行」だけを取り出し、nameprice の2カラムを返します。

SELECT * と書けば全カラム取得。WHERE を省略すれば全行取得。複数条件は ANDOR で結合します:

SELECT * FROM members
WHERE member_id = 'admin' AND password = 'secret'

Lab 02 のログインバイパスは、まさにこの2条件のうち後者を消す攻撃。

7. 文字列リテラルとシングルクォート #string-literal

SQL で「これは文字列です」を示す印が '

SQL の中で文字列(テキスト)はシングルクォート(')で囲んで書きます。これを文字列リテラルと呼びます。

SELECT * FROM products WHERE category = 'スキンケア'
                                          ↑    ↑
                                  ここで開いて  ここで閉じる

数値の場合はクォートで囲みません(WHERE id = 5)。文字列だけがクォートで囲まれます。

これが SQLi の核

もし、ユーザが入力した値がそのまま'...'の中に入る実装になっていて、ユーザが入力に ' を含めたら——シングルクォートが「文字列を閉じる印」として解釈されてしまい、その後ろに任意の 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 のサイト側の対策セクションで。