CORSの設定ミス(CORS misconfiguration・CWE-942)は、本来そのサイトからしか読めないはずのAPI応答を、他のサイトに読ませてしまう欠陥です。ブラウザには「同一オリジンポリシー」があり、あるサイトのページが別オリジンのAPIの応答を勝手に読むことを禁じています。CORSは、サーバーが「このオリジンになら読ませてよい」と明示的に許可するための仕組みです。ここで許可の出し方を誤ると、利用者がログイン中に攻撃者のサイトを開いただけで、その応答(個人情報やトークン)を攻撃者のスクリプトに読み取られます。

本記事は連載「事故から逆算するWebセキュリティ」の第28回です。APIにCORSを設定する開発者に向けて、何が危険で、どう設定するかを整理します。セキュリティ全体の地図は「サプライチェーン攻撃とは|手口・防御ツール比較・npm 12の新機構まで実践解説」にまとめています。

REFLECT vs ALLOWLIST根っこは『誰に読ませるかを入力に決めさせるか』

Originをそのまま反射は危険/完全一致の許可リストなら安全

危ない:Origin を反射

  • リクエストの Origin を ACAO にそのまま返す
  • さらに Allow-Credentials: true を付ける
  • → どの悪意サイトも認証済み応答を読める
  • null 許可・前方/後方一致も同じ穴

安全:許可リスト+最小

  • 信頼する Origin を完全一致の許可リストで照合
  • Credentials は信頼 Origin のときだけ true
  • 機密APIに Allow-Origin: * を付けない
  • CORS を認可の代わりにしない
出典: MDN CORS / OWASP CORS, CWE-942 の方針(2026-11-06 整理)

30秒でわかる

  • ・何が起きるか:CORSを緩く設定すると、認証済みのAPI応答を他オリジン(攻撃者サイト)に読み取られる(CWE-942)
  • ・典型の誤設定:リクエストの Origin をそのまま Access-Control-Allow-Origin に返し、Allow-Credentials: true も付ける
  • ・なぜ危険か:実質どのオリジンにもCookie付き応答の読み取りを許すことになる。null許可・前方/後方一致も同じ穴
  • ・効く対策:信頼するOriginを完全一致の許可リストで照合し、Credentialsは必要なときだけ。CORSは認可の代わりにしない

CORSはよくCSRFとSameSiteの今と混同されますが、別物です。CORSは「他オリジンが応答を読めるか」、CSRFは「他オリジンから勝手に操作を送られること」の話です。読み取れた応答からセッション情報が漏れる筋はセッション管理とCookieの属性とも繋がります。

CORSは「読み取りの許可」であって、アクセス制御ではない

まず前提を正します。影響を受けるのは、認証済み(Cookieやトークン付き)の応答を返すAPIで、CORSの許可範囲を広げすぎた場合です。CORSは、サーバーがレスポンスヘッダ Access-Control-Allow-Origin(ACAO)で「このオリジンには読ませてよい」と宣言し、Cookieなどの資格情報を伴う場合は Access-Control-Allow-Credentials: true(ACAC)を併せて返す仕組みです。

ここで誤解されがちな点が二つあります。一つは、CORSはサーバー側のアクセス制御ではないこと。CORSが決めるのは「ブラウザが他オリジンに応答の中身を読ませるか」だけで、curl のような非ブラウザのクライアントはCORSを無視して応答を受け取れます。だからCORSを認証・認可の代わりにはできません。もう一つは、プリフライト(OPTIONS)は『操作の可否』を保証しないこと。単純リクエストはプリフライトを待たずにサーバーへ届くため、CORSを厳しくしてもCSRFは防げません。

よくあるCORSの設定ミス

COMMON MISTAKES『読めてしまう』誤設定

反射・緩い一致・ワイルドカードの誤解

  1. 1.Origin をそのまま反射+Credentials

    Access-Control-Allow-Origin にリクエストの Origin を返し、Allow-Credentials: true。実質『全オリジン許可』になり、Cookie付きの応答を他サイトが読める

  2. 2.null を許可

    Origin: null を許可すると、サンドボックス iframe などから読めてしまう

  3. 3.前方一致・後方一致・雑な正規表現

    startsWith('https://example.com') や endsWith('example.com') は example.com.evil.com や evilexample.com を通す

  4. 4.* + Credentials の誤解

    Allow-Origin: * と Credentials: true は併用不可(ブラウザが拒否)。だから反射で回避、という発想が事故のもと

出典: OWASP / PortSwigger の CORS 誤設定パターンを整理(2026-11-06)。悪用PoCは非掲載

危険になるのは、次のような「読めてしまう」設定です。

・Originの反射+Credentials:リクエストの Origin をそのまま Access-Control-Allow-Origin に返し、Allow-Credentials: true を付ける。これは実質「全オリジン許可」で、認証済み応答を任意のサイトが読める
・null の許可:Origin: null を許可すると、サンドボックス化した iframe などから読まれる
・前方一致・後方一致・雑な正規表現:startsWith("https://example.com") は https://example.com.evil.com を、endsWith("example.com") は evilexample.com を通す
・ワイルドカードの誤解:Access-Control-Allow-Origin: * と Allow-Credentials: true はブラウザが併用を拒否する。これを「反射すれば回避できる」と考えるのが事故のもと

要は、守るべきは「ヘッダの付け方のテクニック」ではなく、誰に読ませるかを入力(Originヘッダ)に決めさせないことです。攻撃者が送れる入力と、守りが無いときに起こることを整理します。

攻撃者が送れる入力 守りが無いと起こること
任意の Origin ヘッダ 反射されて自分のサイトが許可され、認証済み応答を読む
Origin: null null許可の設定だと読み取りが通る
正規ドメインに似せたオリジン 前方/後方一致・雑な正規表現を通り抜ける
(CORSを無視する非ブラウザ) CORSは効かない=認可で守るしかない

CORSの設定ミス対策——完全一致の許可リストで絞る

効くのは、ヘッダの工夫ではなく、信頼するオリジンを完全一致の許可リストで照合し、必要最小限だけ許可することです。MDN と OWASP も、ACAOにリクエストのOriginを無条件に反射しないこと、資格情報付きで許すオリジンを限定することを勧めています。順序はこうです。

SAFE DESIGN設計の順序

反射しない → 完全一致許可リスト → Credentialsは最小 → 認可はCORSに頼らない

  1. 1

    Origin を反射しない

    受け取った Origin をそのまま Allow-Origin に返さない。許可リストに無ければ CORS ヘッダを付けない

  2. 2

    完全一致の許可リスト

    信頼する Origin を完全一致で照合(scheme+host+port)。前方/後方一致や雑な正規表現を使わない

  3. 3

    Credentials は必要なときだけ

    Allow-Credentials: true は本当に必要な信頼 Origin に限る。公開APIは Credentials 無し+必要なら Origin: *

  4. 4

    CORS を認可の代わりにしない

    CORS はブラウザの読み取り可否を決めるだけ。非ブラウザ(curl 等)は無視する。認証・認可・CSRF対策は別に用意する

出典: MDN CORS / OWASP の方針(2026-11-06)。CORS はアクセス制御ではなくブラウザの緩和機構

ポイントは、Originヘッダを信用して反射しないことです。受け取ったOriginが許可リスト(scheme+host+portの完全一致)にあるときだけ、そのOriginをACAOに返します。無ければCORSヘッダを付けません。守り方ごとに、何を防ぎ、何を防がないかを並べます。

守り方 何を防ぐか 何を防がないか
Originの反射+Credentials (防がない。実質全許可で危険) 任意オリジンの読み取り
前方/後方一致での許可判定 ごく単純な別オリジン 似せたドメイン(.evil.com 等)
完全一致の許可リスト 許可外オリジンの読み取り 許可オリジン自体のXSS・乗っ取り
公開API=Credentials無し+必要なら * 資格情報付き読み取りの事故 公開データの性質上の露出(元々公開)

加えて、Credentials(Cookie・認証情報)を伴う応答を返すのは、本当に必要な信頼オリジンに限ることです。公開データだけのAPIなら、Credentialsを使わず、必要に応じて * を使うほうが安全です。そして繰り返しになりますが、CORSは認可の代わりになりません。読ませてよい相手かはCORSで、操作してよい相手かは認証・認可とCSRF対策で、それぞれ別に守ります。

もう一つ、見落とされがちな観点があります。CORSの許可リストに載せた信頼オリジンがそれ自体で安全とは限らないことです。許可したオリジンにXSSがあれば、攻撃者はそのオリジンのコンテキストからあなたのAPIを正規に読めてしまいます。サブドメインをまるごと許可(ワイルドカード)している場合はさらに広く、乗っ取られた、あるいは第三者に払い出した一つのサブドメインが踏み台になります。許可リストは「信頼できるオリジンを最小限だけ」に保ち、サブドメイン全体の許可は避けるのが安全です。

また、CORSの設定ミスは「静かに広がる」点にも注意が要ります。開発中に動作確認のため一時的に Access-Control-Allow-Origin を全反射にして、そのまま本番へ出てしまう、という経緯が実際によくあります。開発用の緩い設定を本番に残さないよう、環境ごとに許可オリジンを分け、本番の許可リストをコードレビューとデプロイ前チェックの対象に含めておくと、この種の取りこぼしを防げます。

設定の要点(ライブラリ任せにしない)

多くのフレームワークにCORSミドルウェアがありますが、既定や例のままだと危険なことがあります。要点は次のとおりです。

  • origin: true/全反射に注意:一部のライブラリの origin: true は「リクエストのOriginを反射する」挙動になることがある。許可リスト(配列)や関数で、完全一致の判定を明示する。
  • Credentials と反射を同時に使わない:資格情報を許すなら、Originは必ず限定した完全一致にする。
  • ワイルドカードの範囲を絞る:* は公開・非資格情報のAPIに限る。サブドメインワイルドカードを安易に許さない(乗っ取られたサブドメインが踏み台になる)。
  • プリフライトのキャッシュと許可メソッド/ヘッダも最小に:Access-Control-Allow-Methods / -Headers は必要なものだけに絞る。

自分のコードを確認する

ここからは読み取りだけの確認コマンドです。いずれも判定ではなく構成の確認で、ヒットの有無だけでは安全とは言えません。該当箇所を目で追う起点として使います。. は自分のリポジトリのルートに読み替えてください。

まず、CORS関連のヘッダ・設定を洗い出します。

grep -rniE "(Access-Control-Allow-Origin|Access-Control-Allow-Credentials|cors\(|allow_origins|allowed_origins|CORSMiddleware)" . --include="*.py" --include="*.js" --include="*.ts" --include="*.go" --include="*.rb" --include="*.java" --include="*.php" --include="*.conf"

次に、危険な「Originの反射」や「全許可」が無いかを確かめます。リクエストのOriginを変数で ACAO に入れている箇所、origin: true、ワイルドカードとCredentialsの組み合わせがヒットしたら、完全一致の許可リストへ直します。

grep -rniE "(Allow-Origin.*(req|request|headers|origin)|origin:\s*true|allow_credentials\s*=\s*True|credentials:\s*true)" . --include="*.py" --include="*.js" --include="*.ts" --include="*.go" --include="*.rb" --include="*.java" --include="*.php"

いずれも、小さなダミーのプロジェクトで陽性(Originを反射+Credentialsにした側)と陰性(完全一致の許可リストに直した側)の両方を走らせ、拾うべきものを拾い、無いときに何も出ないことを確かめてから載せています。ヒットした箇所が本当に危険かは、そのエンドポイントが認証済み応答を返すか、Originの判定が完全一致かを、コードを読んで確かめます。悪用の具体的な手順は本記事では扱いません。

参照ソース