• 診断項目 No.125/127
  • CORS(クロスオリジン設定)
  • 重大度 重要(high)

CORSプリフライトの許可範囲とは?危険な設定パターン・許可リストでの設定方法・診断基準

CORSプリフライトの許可範囲は、OPTIONSによる事前確認への応答で、Access-Control-Allow-Origin・Methods・Headersをどこまで許すかの設定です。未許可Originを許す危険な例と、完全一致の許可リストで絞る方法を示します。

結論(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プリフライトの許可範囲
項目IDcors.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: nullsandbox属性付き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-07CORSの検査
CWE-942信頼できないドメインに対する寛容なクロスドメインポリシー
WHATWG Fetch StandardCORSプロトコルの仕様

CORSについてよくある誤解

  • 「CORSはサーバーを守る仕組み」:CORSはブラウザが応答の読み取りを制御するもので、サーバーへのリクエスト自体は届きます。CSRF対策の代わりにはなりません。
  • 「Originを部分一致で検証すれば十分」:example.lg.jp を含むかどうかで判定すると、example.lg.jp.attacker.example のようなドメインを許してしまいます。完全一致の許可リストで検証します。
  • 「* はいつも危険」:認証を伴わない公開データ(オープンデータ等)では、* が正しい設定です。

CORSプリフライトの許可範囲の診断基準(セキュリティAIチェッカー)

取得方法

  1. API候補:V2診断で見つかった同一オリジンの /api/ 配下URL、.json のURLなど(最大2件)の先頭1件。候補がなければnaです。
  2. 送信内容:メソッド OPTIONS、ヘッダー Origin: https://miniparse-invalid-origin.example、Access-Control-Request-Method: POST、Access-Control-Request-Headers: authorization,content-type。リダイレクトは追跡せず、タイムアウト5秒、本文上限20KB。
  3. 応答の 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)

基本方針

  1. 他オリジンから使わせる必要がないAPIでは、CORSヘッダーを一切返さない。
  2. 必要な場合は、許可するOriginを完全一致の許可リストで管理し、一致したときだけその値を返す。
  3. Allow-Credentials: true は、Cookie認証が本当に必要な連携だけに限定する。
  4. 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に許可が出ることがあります。

セキュリティAIチェッカーを利用する

この項目を含む127項目を、URLを入れるだけで無料診断します。

参考資料