結論(CORSプリフライトの許可範囲)
CORSプリフライトの許可範囲とは、ブラウザが自動で送るOPTIONSリクエストに対して、サーバーがAccess-Control-Allow-Originなどで許可するオリジン・メソッド・ヘッダーの範囲です。このOPTIONSリクエストは、他オリジンのJavaScriptがAPIへ単純でないリクエストを送る前に送られます。受け取ったOriginをそのまま返す反射や、null許可、*と認証情報の併用は、攻撃者のサイトにログイン中の利用者のデータを読ませる危険があるため、完全一致の許可リストで検証することが推奨されます。
この記事のポイント
- CORSは同一オリジンポリシーを、サーバーの明示的な許可がある場合に限って緩める仕組みです。
- Origin反射とAccess-Control-Allow-Credentials: trueの併用は、任意サイトがログイン中の利用者のデータを読める最も危険な設定です。
- Access-Control-Allow-Origin: nullはsandbox iframe等から送れるため、実質的に誰でも許可されます。
- セキュリティAIチェッカーは未許可Originでプリフライトを送り、反射や認証情報付きの*・nullを×、nullのみを△と判定します。
- 公開データのAPIでは、認証情報なしの「*」は正しい設定です。
| 診断項目名 | CORSプリフライトの許可範囲 |
|---|---|
| 項目ID | cors.preflight-policy |
| 分類 | CORS(クロスオリジン設定) |
| 重大度・配点の重み | 重要(high) 重み 4 |
| 関連する標準・ガイドライン | OWASP ASVS 5.0.0 V3.4.2(L1:Access-Control-Allow-Originは固定値、またはOriginを信頼済み許可リストで検証した値とし、* を使う場合は機密情報を含めない)、ASVS V4.1.4(L3:プリフライトのOPTIONSを含め、明示的にサポートするメソッドだけを使用可能にする)、OWASP WSTG-CLNT-07(Testing Cross Origin Resource Sharing)、CWE-942(Permissive Cross-domain Security Policy with Untrusted Domains)、WHATWG Fetch Standard(CORS protocol) |
CORSプリフライトの許可範囲とは
CORSプリフライトの許可範囲とは、他オリジンのJavaScriptがAPIへ「単純でない」リクエストを送る前にブラウザが自動で送る OPTIONS リクエスト(プリフライト)に対して、サーバーが Access-Control-Allow-Origin・Access-Control-Allow-Methods・Access-Control-Allow-Headers・Access-Control-Allow-Credentials で許可する範囲のことです。
CORS(オリジン間リソース共有)は、ブラウザの同一オリジンポリシーを、サーバーの明示的な許可がある場合に限って緩める仕組みです。許可の判定を誤ると、利用者が攻撃者のサイトを開いただけで、ログイン中のAPIの応答(個人情報など)を攻撃者のJavaScriptが読み取れるようになります。
セキュリティAIチェッカーのこの項目は、サイト内で見つかったAPI候補に、実在しない外部オリジン(https://miniparse-invalid-origin.example)からのCORSプリフライトを模したOPTIONSリクエストを送り、サーバーがその未許可オリジンに読み取りを許してしまわないかを確認します。
プリフライトの流れ
JavaScriptが他オリジンへ、POST の application/json 送信や Authorization ヘッダー付きのリクエストなど「単純でない」リクエストを送る前に、ブラウザは自動で次のようなOPTIONSを送ります。
OPTIONS /api/data HTTP/1.1
Origin: https://other.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-type
サーバーは許可する場合に次のように応答し、ブラウザはその内容に合致したときだけ本来のリクエストを送ります。
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://other.example
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: authorization,content-type
Access-Control-Max-Age: 600
Vary: Origin
プリフライト自体にはCookie等の認証情報は付きません。認証情報付きの本リクエストを許すには、Access-Control-Allow-Credentials: true が必要です。
CORSプリフライトの許可範囲はなぜ重要なのか
危険な設定パターン
| パターン | 応答 | 評価 |
|---|---|---|
| Origin反射 | 受け取った Origin をそのまま Access-Control-Allow-Origin に返す | 許可リスト検証をしていない。Allow-Credentials: true と組み合わさると、任意サイトがログイン中の利用者のデータを読める(最も危険) |
| null許可 | Access-Control-Allow-Origin: null | sandbox属性付きiframeやローカルファイル等から Origin: null を送れるため、実質的に誰でも許可される。PortSwiggerも典型的な脆弱性として挙げている |
* と認証情報の併用 | Allow-Origin: * と Allow-Credentials: true | ブラウザは認証情報付きリクエストでこの組み合わせを拒否するため直接の悪用はできないが、仕様に反する設定で、許可方針が整理されていない兆候 |
* のみ | Allow-Origin: *(認証情報なし) | 公開データのAPIなら問題ない。ASVS V3.4.2も、* を使う場合は機密情報を含まないことを条件に許容 |
標準・ガイドラインの要求
| 出典 | 要求 |
|---|---|
| OWASP ASVS 5.0.0 V3.4.2(L1) | Access-Control-Allow-Origin は固定値、またはOriginを信頼済み許可リストで検証した値とし、* を使う場合は機密情報を含めない |
| OWASP ASVS 5.0.0 V4.1.4(L3) | プリフライトのOPTIONSを含め、明示的にサポートするメソッドだけを使用可能にする |
| OWASP WSTG-CLNT-07 | CORSの検査 |
| CWE-942 | 信頼できないドメインに対する寛容なクロスドメインポリシー |
| WHATWG Fetch Standard | CORSプロトコルの仕様 |
CORSについてよくある誤解
- 「CORSはサーバーを守る仕組み」:CORSはブラウザが応答の読み取りを制御するもので、サーバーへのリクエスト自体は届きます。CSRF対策の代わりにはなりません。
- 「Originを部分一致で検証すれば十分」:
example.lg.jpを含むかどうかで判定すると、example.lg.jp.attacker.exampleのようなドメインを許してしまいます。完全一致の許可リストで検証します。 - 「
*はいつも危険」:認証を伴わない公開データ(オープンデータ等)では、*が正しい設定です。
CORSプリフライトの許可範囲の診断基準(セキュリティAIチェッカー)
取得方法
- API候補:V2診断で見つかった同一オリジンの
/api/配下URL、.jsonのURLなど(最大2件)の先頭1件。候補がなければnaです。 - 送信内容:メソッド
OPTIONS、ヘッダーOrigin: https://miniparse-invalid-origin.example、Access-Control-Request-Method: POST、Access-Control-Request-Headers: authorization,content-type。リダイレクトは追跡せず、タイムアウト5秒、本文上限20KB。 - 応答の
Access-Control-Allow-OriginとAccess-Control-Allow-Credentialsを判定します。
判定条件
| 判定 | 条件 | 得点への影響 |
|---|---|---|
| × 要改善(fail) | Access-Control-Allow-Origin が送信した未許可Originと完全一致(反射) | 重み4 × 0 |
| × 要改善(fail) | Access-Control-Allow-Origin: * かつ Access-Control-Allow-Credentials: true | 重み4 × 0 |
| × 要改善(fail) | Access-Control-Allow-Origin: null かつ Access-Control-Allow-Credentials: true | 重み4 × 0 |
| △ 要確認(warn) | Access-Control-Allow-Origin: null(認証情報許可なし) | 重み4 × 0.45 |
| ○ 問題なし(pass) | 上記以外の値(* のみ、または自サイト等の固定値)で、未許可Originを許可していない | 重み4 × 1.00 |
| 対象外(na) | API候補がない/プリフライト応答に Access-Control-Allow-Origin がない(CORS非対応)/応答を取得できない | 得点計算から除外 |
- 反射は、
Allow-Credentialsの有無にかかわらずfailとします。ASVS V3.4.2がOriginの値を使う場合は許可リストでの検証を求めており、反射はその検証がないことを直接示すためです。 Access-Control-Allow-Originがない応答は、そのAPIが他オリジンからの読み取りを許していないことを意味するため、安全側としてnaにしています。- 実際のGET応答での反射は、別項目「任意Originを反射しない」、
Vary: Originの有無は「動的CORSでVary: Originを返す」で判定します。OPTIONSのAllowに並ぶメソッドは「OPTIONSが示す不要・危険なHTTPメソッド」で確認します。
自治体サイトでの判定例
| 状況 | 判定 |
|---|---|
多くの自治体CMSサイトは /api/ を持たない | 対象外 |
オープンデータの /api/facilities.json が Access-Control-Allow-Origin: *(認証情報なし) | ○ |
施設予約システムのAPIがフレームワークのCORS設定 origin: true, credentials: true のまま公開され、任意Originを反射 | × |
開発時の設定が残り Access-Control-Allow-Origin: null を返す | △(認証情報許可付きなら×) |
CORSプリフライトの許可範囲が広すぎるとどんなリスクがあるか
- ログイン中データの窃取:Origin反射と認証情報許可が併用されていると、利用者が攻撃者のページを開いただけで、利用者のCookie付きでAPIを呼び出し、予約情報、申請内容、氏名・連絡先などを読み取られます。PortSwiggerのJames Kettle氏は2016年の研究発表で、この種の設定不備が実在の多数のサイトに見られ、暗号資産取引所のAPIキー窃取につながる例があったことを報告しています。
- イントラネット資源の踏み台:認証情報を使わなくても、IPアドレス制限だけで守られた内部APIが
*や反射でCORSを許していると、外部サイトが職員のブラウザを経由して内部データを読む攻撃が成立します。 - キャッシュ汚染:Originごとに応答を変えるのに
Vary: Originがないと、CDNが別Origin向けの許可ヘッダーをキャッシュし、正当な利用者で失敗する、または意図しないOriginに許可が出ることがあります。
CORSプリフライトの安全な設定方法(Apache・nginx・Next.js)
基本方針
- 他オリジンから使わせる必要がないAPIでは、CORSヘッダーを一切返さない。
- 必要な場合は、許可するOriginを完全一致の許可リストで管理し、一致したときだけその値を返す。
Allow-Credentials: trueは、Cookie認証が本当に必要な連携だけに限定する。Allow-MethodsとAllow-Headersも必要最小限に絞り、Vary: Originを付ける。
Apache(.htaccess)
<IfModule mod_headers.c>
SetEnvIf Origin "^https://(www\.example\.lg\.jp|map\.example\.lg\.jp)$" CORS_ALLOW=$0
Header always set Access-Control-Allow-Origin "%{CORS_ALLOW}e" env=CORS_ALLOW
Header always set Access-Control-Allow-Methods "GET, POST" env=CORS_ALLOW
Header always set Access-Control-Allow-Headers "Content-Type" env=CORS_ALLOW
Header always merge Vary "Origin"
</IfModule>
正規表現は ^ と $ で全体一致にし、ドットはエスケープします。
nginx
map $http_origin $cors_origin {
default "";
"https://www.example.lg.jp" $http_origin;
"https://map.example.lg.jp" $http_origin;
}
server {
location /api/ {
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Vary Origin always;
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Methods "GET, POST" always;
add_header Access-Control-Allow-Headers "Content-Type" always;
add_header Access-Control-Max-Age 600 always;
add_header Vary Origin always;
return 204;
}
}
}
値が空の add_header は送信されないため、許可リスト外のOriginにはヘッダーが付きません。
Next.js(Route Handler)
const ALLOWED = new Set(["https://www.example.lg.jp"]);
function corsHeaders(origin: string | null) {
const headers = new Headers({ Vary: "Origin" });
if (origin && ALLOWED.has(origin)) {
headers.set("Access-Control-Allow-Origin", origin);
headers.set("Access-Control-Allow-Methods", "GET, POST");
headers.set("Access-Control-Allow-Headers", "Content-Type");
}
return headers;
}
export async function OPTIONS(request: Request) {
return new Response(null, { status: 204, headers: corsHeaders(request.headers.get("origin")) });
}
設定後の確認方法
# 未許可Originでのプリフライト
curl -s -o /dev/null -D - -X OPTIONS https://www.example.lg.jp/api/data \
-H 'Origin: https://evil.example' \
-H 'Access-Control-Request-Method: POST' \
-H 'Access-Control-Request-Headers: content-type' | grep -i '^access-control-'
# null Origin
curl -s -o /dev/null -D - https://www.example.lg.jp/api/data -H 'Origin: null' | grep -i '^access-control-'
未許可Originや null に対して Access-Control-Allow-Origin が返らなければ対応済みです。
サイトを壊さないための注意点
- 許可リストを厳しくすると、地図・予約・外部委託の画面など既存の連携が動かなくなることがあります。適用前に、どのOriginから呼ばれているかをアクセスログの
Originで棚卸ししてください。 - フレームワークの「全Origin許可」設定(例:
origin: true、AllowAnyOrigin())は、開発用の設定が本番に残っていないか確認してください。 - CDNを使う場合は
Vary: Originを必ず付けます。
よくある質問(FAQ)
CORSを設定すればサーバーを攻撃から守れますか?
守れません。CORSはブラウザが応答の読み取りを制御する仕組みで、サーバーへのリクエスト自体は届きます。CSRF対策の代わりにはなりません。
CORSプリフライトの応答で、Allow-MethodsとAllow-Headersはどう設定すればよいですか?
必要最小限に絞ります。このページの設定例では、許可リストに一致したOriginに対してだけ Access-Control-Allow-Methods: GET, POST と Access-Control-Allow-Headers: Content-Type を返し、Vary: Origin を付けています。OWASP ASVS 5.0.0 V4.1.4(L3)も、プリフライトのOPTIONSを含め、明示的にサポートするメソッドだけを使用可能にすることを求めています。
Originを部分一致で検証してもよいですか?
よくありません。example.lg.jpを含むかどうかで判定すると、example.lg.jp.attacker.example のようなドメインを許してしまいます。完全一致の許可リストで検証してください。
CORSプリフライトの応答で Access-Control-Allow-Origin: null を返すと何が問題ですか?
sandbox属性付きiframeやローカルファイル等からOrigin: nullを送れるため、実質的に誰でも許可されます。認証情報許可と組み合わさると×、認証情報許可がなくても△と判定します。
プリフライト応答にCORSヘッダーがないと、どう判定されますか?
対象外(na)です。Access-Control-Allow-Originがない応答は、そのAPIが他オリジンからの読み取りを許していないことを意味するため、安全側として採点から除外しています。API候補が見つからない場合も対象外です。
CDNを使っている場合に気を付けることはありますか?
Originごとに応答を変える場合はVary: Originを必ず付けます。ないとCDNが別Origin向けの許可ヘッダーをキャッシュし、正当な利用者で失敗する、または意図しないOriginに許可が出ることがあります。
この項目を含む127項目を、URLを入れるだけで無料診断します。
参考資料
- OWASP ASVS 5.0.0 V3 Web Frontend Security(3.4.2)
- OWASP WSTG Cross Origin Resource Sharing(WSTG-CLNT-07)
- MDN Cross-Origin Resource Sharing (CORS)
- WHATWG Fetch Standard(CORS protocol)
- PortSwigger Web Security Academy: Cross-origin resource sharing (CORS)
- PortSwigger Research: Exploiting CORS misconfigurations for Bitcoins and bounties