まとめに
― User-Agent を侵入口にした “ログ経由 SQLi” の仕組みと攻撃手法 ―
現代の Web アプリでは、User-Agent や Referer といった HTTP ヘッダーを「ただのログ情報」として扱いがちです。
しかし、これらはすべて ユーザーが自由に書き換え可能な“入力値”。
そして入力値が SQL にそのまま埋め込まれた瞬間──
SQL Injection の大門が開きます。
(門番は爆睡しています。攻撃者はすっと通り抜けます。)
この記事では、User-Agent を利用した HTTP Header Injection → SQL Injection の流れを、実際の脆弱コードと攻撃パターンを交えて徹底的に解説していきます。
1. シナリオ概要:User-Agent が DB に保存されるアプリ
対象アプリは次のような挙動を持っています。
/httpagent/ にアクセスすると
- サーバがリクエストの User-Agent を取得
- その値を logs テーブルに INSERT
- あとで同じ値で SELECT して表示
User-Agent の流れ:
HTTP Request → User-Agent → INSERT INTO logs → SELECT FROM logs → Webに表示
ログを残すだけの“ harmless 処理 ”に見えて、
ここに SQL Injection の地雷が全力で埋まっています。
2. 問題のサーバーコード(脆弱版)
PHP(mysqli)で書かれたコードはこんな感じ:
$userAgent = $_SERVER['HTTP_USER_AGENT'];
$insert_sql = "INSERT INTO logs (user_Agent) VALUES ('$userAgent')";
$conn->query($insert_sql);
$sql = "SELECT * FROM logs WHERE user_Agent = '$userAgent'";
$result = $conn->query($sql);
はい、犯人はココです。
- ユーザー入力
- エスケープなし
- SQLに直埋め込み
という 3 連コンボ。
User-Agent は curl や Burp Suite なら自由に書き換えられるため、
攻撃者は SQL 文を丸ごと乗っ取る ことができます。
3. User-Agent を悪用した攻撃の流れ
3.1 Step1:まずは SQL が壊せることを確認(構文エラー)
User-Agent: test'--
これが SQL になると:
INSERT INTO logs (user_Agent) VALUES ('test'--');
-- 以降はコメント化されるため、
構文が崩れ、DB がエラーを返します。
この瞬間、
ヘッダーを経由して SQL が“壊せる”ことが確定
つまり SQL Injection 攻撃が実行可能であるという合図です。
3.2 Step2:ログ閲覧時の SELECT を乗っ取る
次のような User-Agent を送る:
User-Agent: ' OR 1=1--
すると SELECT はこうなる:
SELECT * FROM logs WHERE user_Agent = '' OR 1=1--';
OR 1=1 により条件が常に TRUE になるため、
logs テーブルの全レコードが表示される。
小手調べの OR 1=1 攻撃がここで成立します。
3.3 Step3:UNION を使って別テーブルのデータを盗む
本命の攻撃はこちら:
User-Agent: ' UNION SELECT username, password FROM user; --
SQL はこうなる:
SELECT * FROM logs WHERE user_Agent = ''
UNION
SELECT username, password FROM user;
-- ';
テーブル構造が一致していれば…
- logs の内容
- user テーブルの username / password
これらが 1 つのレスポンスに並んで返ってきます。
つまり攻撃者のブラウザに ユーザー名リスト & パスワード(ハッシュ) が露出。
ログ表示ページが、データ漏洩の出入口になってしまう わけです。
4. なぜ「HTTP Header Injection」になるのか?
フォーム入力だけが危険、という時代は終わりました。
攻撃者が自由に変更できるものは、すべて 入力値 です。
- User-Agent
- Referer
- X-Forwarded-For(IP偽装用、危険度MAX)
- Cookie
- Host ヘッダー
これらを信じて SQL に流すと、
すべて SQL Injection の踏み台になる。
User-Agent を通じた
ハイブリッド型の Second-Order SQL Injection に近い挙動になります。
5. 防御:パラメータ化クエリ以外は信じるな
NG:自前エスケープ
$ua = $conn->real_escape_string($_SERVER['HTTP_USER_AGENT']);
→ 漏れたら即死。
→ エスケープ対象外の DB(MySQL→PostgreSQL)で死ぬ。
→ UNION, コメント, エスケープ外手法に弱い。
最強の防御:Prepared Statement(プリペアドステートメント)
// INSERT
$stmt = $conn->prepare("INSERT INTO logs (user_Agent) VALUES (?)");
$stmt->bind_param("s", $userAgent);
$stmt->execute();
// SELECT
$stmt = $conn->prepare("SELECT * FROM logs WHERE user_Agent = ?");
$stmt->bind_param("s", $userAgent);
$stmt->execute();
これで User-Agent に ' UNION SELECT … を入れても、
SQL 文には決して変形しません。
値として扱われるので、SQL が壊れない。
これが唯一確実な防御です。
6. ログだから安全? → 安全ではありません
「ログに入れるだけだから無害」と考えるのは危険です。
- ログの閲覧画面で SQL に再投入される
- ログを CSV に落として別処理でパースされる
- ログの全文検索で SQL を使っている
- 管理画面でフィルタ条件に利用している
ログに入った瞬間は harmless に見えても、
二次利用したときに爆発するのが Second-Order SQL Injection。
つまり「ログでも絶対にサニタイズ必要」。
まとめ:User-Agent の一文字がデータベースを破壊する
| 項目 | 内容 |
|---|---|
| 攻撃入口 | User-Agent などの HTTP ヘッダー |
| 原因 | SQL に直接値を埋め込む(非パラメータ化) |
| 主な攻撃 | OR 1=1 / UNION SELECT / コメント注入 |
| 発生ポイント | ログ記録処理、ログ閲覧処理 |
| 防御 | Prepared Statement 一択 |
HTTP ヘッダーは“信頼できないユーザー入力”の代表格です。
ログに保存するだけ、と思っても、そのログが後で SQL に再利用されれば即アウト。
「ヘッダー=危険」と認識し、必ずパラメータ化クエリを使う。
これだけで、多くの攻撃を根絶できます。