APIに Access-Control-Allow-Origin を付けたら動いた、というだけでは安全とは限りません。CORSは付ければ済むものではなく、許可する相手を絞ることが大切です。仕組みと安全な設定を解説します(攻撃手順は載せません)。
まず仕組み
ブラウザには「別オリジンのJSは勝手に他サイトの応答を読めない」という同一オリジンポリシーがあります。CORSは、その例外をサーバーが明示的に許可した相手にだけ開くためのものです。
オリジンは「スキーム(https)+ホスト名+ポート番号」の組です。https://example.com と https://api.example.com はホスト名が違うので別オリジン、http://example.com と https://example.com もスキームが違うので別オリジンです。
| 応答ヘッダ | 意味 |
|---|---|
Access-Control-Allow-Origin | どのオリジンに応答の読み取りを許すか |
Access-Control-Allow-Credentials | Cookie等の認証情報を伴う通信を許すか |
Access-Control-Allow-Methods / -Headers | 許可するHTTPメソッド/ヘッダ |
ポイントは、CORSは「制限を緩める」側の仕組みだということ。緩め方を間違えると、本来読めないはずのデータが第三者に読めてしまいます。
もう1つ大事なのは、CORSが止めるのは「応答をJavaScriptが読むこと」だという点です。単純なリクエストはCORSの許可がなくてもサーバーに届き、処理されます。独自ヘッダを付けるなどの場合は、ブラウザが先にOPTIONSメソッドで確認(プリフライト)し、許可されなければ本番のリクエストを送りません。
似た仕組みとの違い
CORSは名前の似た仕組みや、関係の深い脆弱性と混同されがちです。
| 用語 | 何を決めるか | 誰が守るか |
|---|---|---|
| 同一オリジンポリシー | 別オリジンの応答を読ませない(ブラウザの既定) | ブラウザ |
| CORS | 同一オリジンポリシーの例外として、読ませる相手を決める | サーバーが宣言し、ブラウザが守る |
| CSRF | 利用者のログイン状態で、意図しないリクエストを送らせる攻撃 | CSRFトークン、SameSite属性で防ぐ |
| CSP | 自分のページが読み込んでよいスクリプトや接続先を決める | サーバーが宣言し、ブラウザが守る |
CORSは「応答を読めるか」、CSRFは「リクエストを送らせられるか」の問題です。CORSを厳しくしてもCSRFは防げません。
何が危ない設定か
危険な設定(やりがち)
- リクエストの Origin をそのまま反射して許可する
Access-Control-Allow-Origin: *+ 認証情報も許可- 必要のないAPIまで広く別オリジンに開放
安全な設定
- 許可リスト(信頼できる固定のオリジンだけ)
- それ以外は既定で拒否
- 認証情報を伴うなら
*を使わず具体的なオリジンを指定
設定ミスが生まれる典型的な流れ
多くの設定ミスは、CORSエラーを消そうとして起きます。よくある流れは次のとおりです。
- フロントエンドからCookie付きでAPIを呼ぶと、CORSエラーになる
Access-Control-Allow-Origin: *を付ける。しかし認証情報を伴う通信では、ブラウザは*の応答を読ませないので、エラーは消えない- 受け取った
Originをそのまま返す設定に変えると、エラーが消える。同時に、どのサイトからでもログイン済みの応答を読める状態になる
許可リストを作っても、照合の仕方を誤ると同じことが起きます。
- 「
example.comで終わる」で照合すると、evil-example.comのような別のドメインも通る - 「
https://example.comで始まる」で照合すると、https://example.com.evil.netも通る - 開発の都合で
nullオリジンを許可すると、別のサイトが作り出せるnullからの読み取りも許してしまう
照合は、オリジン全体(スキーム・ホスト・ポート)の完全一致で行います。
安全な設定の基本
許可リスト方式にする(最重要)
Originを鵜呑みに反射しない
Origin をそのまま Access-Control-Allow-Origin に echo しない。許可リストと突き合わせて一致したときだけ返す。返す値がリクエストごとに変わるので、Vary: Origin も付けてキャッシュの取り違えを防ぐ。認証情報を伴うなら * を使わない
Access-Control-Allow-Origin: * と Allow-Credentials: true を併用しない。具体的なオリジンを指定する。開く範囲を最小に
* で開けるのは、ログイン不要の公開データだけにする。自分のサイトで確認する方法
自分のAPIに、別のオリジンを名乗るリクエストを送り、応答ヘッダを見ます。自分が管理するAPIにだけ行います。
# 許可していないオリジンを名乗って、応答ヘッダだけを見る
curl -s -o /dev/null -D - -H "Origin: https://example.com" https://あなたのAPI/ | grep -i "^access-control"Access-Control-Allow-Origin: https://example.comが返ったら、Originを反射している。許可リストの照合を入れるAccess-Control-Allow-Origin: *とAccess-Control-Allow-Credentials: trueが両方あったら、設定を見直す- 何も返らないか、自分のフロントエンドのオリジンだけが返るなら問題ない
同じ確認を Origin: null でも行います。所有を確認したサイトなら、サイト総合セキュリティ診断でもOriginの反射と認証情報の許可をまとめて点検できます。ほかのセキュリティヘッダはセキュリティヘッダ診断で確認できます。
出典(一次情報)
- MDN Web Docs「オリジン間リソース共有(CORS)」 — developer.mozilla.org(認証情報付きリクエストでは * を使えない、プリフライトの条件、Vary: Origin)
- WHATWG Fetch Standard「CORS protocol」 — fetch.spec.whatwg.org
- OWASP HTML5 Security Cheat Sheet — cheatsheetseries.owasp.org(信頼するドメインだけを許可し、Originをそのまま返さない)
- PortSwigger Web Security Academy「Cross-origin resource sharing (CORS)」 — portswigger.net(許可リストの照合ミス、null オリジン、CORSはCSRF対策ではない)
- MITRE CWE-942: Permissive Cross-domain Policy with Untrusted Domains — cwe.mitre.org
次に読む
- 用語:XSS とは / CSRF とは / JWT とは
- 解説:セキュリティヘッダの選び方
- 確認:セキュリティヘッダ診断 / サイト総合セキュリティ診断