CyberFix Note
セキュアコーディング

CORSの仕組みと設定ミスを避ける勘所。オリジン間リソース共有を安全に緩める

対象の目安: Webアプリ・API開発者 / 実務

ソウ攻撃・脆弱性リサーチ担当
・ 約12分で読めます
CORSの仕組みと設定ミスを避ける勘所。オリジン間リソース共有を安全に緩める

CORS(Cross-Origin Resource Sharing/オリジン間リソース共有)は、別オリジンのAPIをブラウザから呼び出すときに避けて通れない仕組みです。フロントエンドとAPIを別ドメインで運用したり、外部のサービスからデータを取得したりすると、ブラウザのコンソールにCORS関連のエラーが出て手が止まる、という経験を持つ開発者は多いはずです。ここで「エラーが消えればよい」と安易にワイルドカードで全許可してしまうと、資格情報を伴わずに読めるレスポンスやCookie以外の手段で認証するAPIを、外部サイトへ開いてしまう設定ミスにつながります。

この記事では、WebアプリとAPIを開発する担当者に向けて、CORSが同一オリジンポリシーの何をどう緩める仕組みなのかを、MDNやWHATWG Fetch Standardなどの技術資料に基づいて整理します。プリフライトと資格情報付きリクエストの扱い、Access-Control-Allow-Originのワイルドカードやオリジン反射といった典型的な設定ミス、そしてサーバー側の許可リストで厳密に照合する安全な設計まで、順を追って説明します。

同一オリジンポリシーとCORSの関係

前提となるのが同一オリジンポリシーです。ブラウザは、あるオリジン(スキームとホストとポートの組)で読み込まれた文書やスクリプトが、別オリジンのリソースのレスポンス内容を読むことを既定で制限します。たとえば https://app.example で動くスクリプトから https://api.other.example のレスポンスを読み取ることは、既定ではできません。これはログイン済みの利用者のブラウザを踏み台に、悪意あるサイトが他サービスの機微なデータを勝手に読み出すことを防ぐための、基礎的な仕切りです。

しかし現実には、正当な理由で別オリジンのAPIを呼びたい場面があります。そこで使うのがCORSです。CORSは、サーバーがHTTPレスポンスヘッダ(Access-Control-Allow-Origin など)で、どのオリジンからのアクセスを許すかをブラウザに伝え、同一オリジンポリシーの制限を必要な範囲だけ緩める仕組みです。ここで押さえておきたいのは、CORSはあくまで制限を緩めるためのものであり、それ自体が新しい防御を足すものではない、という点です。設定を緩めれば緩めるほど、読み取りを許す相手が増えることになります。

MDN Web Docsは、同一オリジンポリシーがオリジンをまたいだリソースの読み取りを制限する仕組みであり、オリジンはスキームとホストとポートの組で判定されると説明しています。

プリフライトと資格情報付きの扱い

CORSのリクエストは、単純リクエストとプリフライトを伴うものに分かれます。GETやHEAD、POSTで、かつヘッダやContent-Typeが限られた条件を満たすものは単純リクエストとして扱われ、そのまま本リクエストが送られます。一方、PUTやDELETE、独自ヘッダの付与、application/json の送信などは条件を外れるため、ブラウザが先にOPTIONSメソッドで問い合わせるプリフライトを送ります。

プリフライトでは、ブラウザがこれから送りたいメソッドやヘッダをサーバーへ伝え、サーバーが Access-Control-Allow-MethodsAccess-Control-Allow-Headers で許可を返します。ブラウザはその応答を確認してから本リクエストを送るため、許可されていないメソッドやヘッダはブラウザ側で止まります。

もう一つ注意が要るのが、Cookieなどの資格情報を伴う資格情報付きリクエストです。この場合、レスポンスに Access-Control-Allow-Credentials: true が必要です。そして仕様上、このとき Access-Control-Allow-Origin にワイルドカード(*)は使えず、具体的な単一オリジンを返さなければなりません。

Access-Control-Allow-Origin: https://app.example
Access-Control-Allow-Credentials: true
Vary: Origin

ワイルドカードと資格情報付きの組み合わせがブラウザによって拒否されるのは、Cookieを伴う機微な読み取りを不特定多数へ開かせないための、仕様上の歯止めです。

典型的な設定ミス

CORS関連のエラーを消そうとする過程で、次のような危険な設定に流れてしまうことがあります。何がなぜ危険で、どう直すかを整理します。

設定ミスなぜ危険か直し方
資格情報付きで使えないワイルドカードの代わりに、リクエストのOriginをそのまま反射して返す任意のサイトがCookie付きで機微なAPIのレスポンスを読める状態になり得る許可リストに含まれるオリジンだけを、完全一致で確認してから返す
オリジン照合が緩い(後方一致や部分一致で trusted.example の後ろに攻撃者ドメインを付けた文字列を許す、正規表現の不備)trusted.example.attacker.example のような偽装オリジンが許可され、攻撃者サイトへ読み取りを許す部分一致や自作の正規表現をやめ、許可リストとの完全一致で判定する
Access-Control-Allow-Originnull を許可するサンドボックス化したiframeなどが送る null オリジンから読めてしまうnull を許可しない。正規のオリジンだけを明示的に許可する

もっとも起こりやすいのがOriginの反射です。資格情報付きではワイルドカードが使えないため、送られてきたOriginヘッダの値をそのまま Access-Control-Allow-Origin に書き戻す実装が生まれがちです。これは事実上「どのオリジンからでも読める」設定に等しく、ログイン中の利用者が攻撃者サイトを開いただけで、機微なレスポンスを盗み読みされる余地を作ります。

OWASPは、サーバーがOriginヘッダを十分に検証せずにレスポンスへ反映すると、信頼していないオリジンからのアクセスを許してしまう問題を指摘しています。

注意

CORSの検証は、自分が管理するサービス、または明示的に許可を得た対象に対してのみ行ってください。他者のサービスの設定を無断で試す行為は、法令に抵触するおそれがあります。

安全に緩めるための設計

安全にCORSを設定する基本は、許可する相手をサーバー側で厳密に管理し、緩める範囲を最小限にとどめることです。次の手順で組み立てます。

  1. 1

    許可オリジンをサーバー側の許可リストで持つ

    許可したいオリジンをサーバー側の許可リストとして定義し、リクエストのOriginがそこに完全一致するときだけ、そのオリジンを Access-Control-Allow-Origin に返します。部分一致や自作の正規表現に頼りません。

  2. 2

    Originごとに応答を変えるなら Vary: Origin を付ける

    許可リストとの照合結果に応じて Access-Control-Allow-Origin を出し分ける場合は、応答に Vary: Origin を付けます。これがないと、共有キャッシュが別オリジン向けの応答を誤って再利用するおそれがあります。

  3. 3

    ワイルドカードと資格情報付きを組み合わせない

    Cookieを伴うAPIでは、Access-Control-Allow-Origin にワイルドカードを使わず、許可リストで確認した単一オリジンを返します。逆に資格情報が不要な公開APIなら、そもそもCookieに依存しない設計にします。

  4. 4

    許可するメソッドとヘッダを最小限にする

    Access-Control-Allow-Methods と Access-Control-Allow-Headers には、実際に必要なものだけを列挙します。使わないメソッドや独自ヘッダを広く開けません。

  5. 5

    機微なAPIを不要に別オリジンへ開かない

    社内向けや管理系など機微なエンドポイントは、そもそも別オリジンへ開く必要があるかを見直します。開く必要がなければCORSで緩めないのが最も安全です。

  6. 6

    導入後にプリフライトと応答ヘッダを確認する

    ブラウザの開発者ツールのネットワークタブで、OPTIONSのプリフライトと応答ヘッダを確認し、意図した範囲だけが許可されているかを目視で検証します。

設定の良い例と悪い例をヘッダで対比すると、違いがはっきりします。次は避けたい例です。

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

資格情報付きでワイルドカードを併用するこの組み合わせは仕様上成立せず、ブラウザに拒否されます。仮に反射で回避しようとすれば、前節で述べた読み取りの開放を招きます。許可リストで確認した単一オリジンを返す形へ直すのが正解です。

CORSとCSRFの違い

CORSを学ぶとき混同されやすいのが、CSRFとの関係です。結論から言えば、CORSはCSRFの対策にはなりません。CORSが制御するのは、あくまで別オリジンからのレスポンス内容を読み取れるかどうかです。単純リクエストは本リクエストがサーバーへ送られて処理され、CORSが制限するのはそのレスポンスを読み取れるかどうかにとどまります。プリフライトを伴うリクエストは、プリフライトが許可されなければ本リクエストが送られませんが、プリフライトの要らない単純リクエストが存在する以上、CORSでCSRFを防ぐことはできません。

CSRFは、利用者のブラウザが自動で付けるCookieを悪用し、本人の意図しない状態変更のリクエストを送らせる攻撃でした。この場合、攻撃者はレスポンスを読む必要すらなく、送金や設定変更が実行されれば目的を達します。CORSはレスポンスの読み取りを絞るだけなので、意図しない状態変更の成立を防ぐCSRF対策とは守る対象が異なります。状態変更の防御はCSRFトークンを主軸に置き、SameSite Cookieを組み合わせる多層防御で行う必要があります。SameSite単独で十分かどうかはアプリの構成によります。

あわせて読みたい

CSRFとは何か。仕組みからトークン・SameSite Cookieによる対策まで実務目線で解説

つまりCORSとCSRFは、名前が似ていても目的が別物です。CORSは読み取りの範囲を管理する仕組み、CSRFは意図しない状態変更を防ぐための対策と整理して、両方を適切に設定することが求められます。CORSと合わせて、レスポンスヘッダ全体でブラウザ側の守りを固める考え方も押さえておくと理解が深まります。

あわせて読みたい

セキュリティHTTPレスポンスヘッダで多層防御を固める方法

APIを設計する段階での認証や入力検証の基本と併せて考えると、CORSはその一部として位置づけられます。

あわせて読みたい

APIセキュリティの基本。認証認可・レート制限・入力検証とOWASP API Top 10で守る

まとめ

CORSは、同一オリジンポリシーという既定の制限を、サーバーの許可で必要な範囲だけ緩めるための仕組みです。緩めるための機能である以上、設定を広げるほど読み取りを許す相手が増える、という性質を忘れないことが安全な運用の出発点になります。エラーを消すためだけにワイルドカードや反射へ流れると、機微なレスポンスを外部へ開いてしまいます。

CORS設定の確認リスト

  • 許可オリジンをサーバー側の許可リストで管理し、完全一致で照合しているか
  • リクエストのOriginをそのまま反射して返していないか
  • 後方一致や部分一致、不備のある正規表現でオリジンを判定していないか
  • 資格情報付きリクエストで Access-Control-Allow-Origin にワイルドカードを使っていないか
  • Access-Control-Allow-Origin に null を許可していないか
  • 許可するメソッドとヘッダを必要最小限に絞っているか
  • 状態変更の防御をCORSに期待せず、CSRFトークンや SameSite Cookie で別途行っているか
  • 導入後に開発者ツールでプリフライトと応答ヘッダを確認したか

CORSの設定は、許可リストで完全一致照合し、ワイルドカードと資格情報付きを混ぜず、開く範囲を最小限にとどめます。この基本を守れば、設定ミスのリスクを抑えて別オリジンのAPI連携を実現できます。

出典・参考

この記事をシェア

関連する記事