CORS(Cross-Origin Resource Sharing、オリジン間リソース共有)は、別のオリジンから読み込まれたページのスクリプトに、自分のサーバーのレスポンスを読ませてよいかをHTTPヘッダーで伝える仕組みです。ブラウザの同一オリジンポリシーを、サーバーが明示した範囲だけゆるめます。CORSは守りを強める仕組みではなく、制限を外す仕組みなので、許可する範囲を広げすぎると情報を読み取られる原因になります。
CORSとは
オリジンは、スキーム(https など)、ホスト名、ポート番号の組み合わせです。MDNによると、fetch() や XMLHttpRequest は同一オリジンポリシーに従い、他のオリジンのレスポンスは、そのレスポンスに適切なCORSヘッダーが含まれている場合にしか読み取れません。
たとえば https://app.example.com のページから https://api.example.com のAPIを呼ぶと、ホスト名が違うので別オリジンです。APIのレスポンスに次のヘッダーがあれば、ブラウザはスクリプトにレスポンスを渡します。
Access-Control-Allow-Origin: https://app.example.com
CORSがしていること
- 他のオリジンのスクリプトに、レスポンスの読み取りを許可する
- 許可する範囲はサーバーがヘッダーで決める
- 判定して止めるのはブラウザ
CORSがしていないこと
- サーバーへのリクエストそのものを止める
- ブラウザ以外(curlやスクリプト)からのアクセスを止める
- 認証や認可の代わりになる
OWASPのHTML5セキュリティチートシートは、CORSはCSRFを防ぐものではないとして、CSRF対策は別に必要だと注意しています。CSRFの対策はCSRFの解説にまとめています。
単純リクエストとプリフライト
CORSのリクエストは、ブラウザが事前確認をせずに送る「単純リクエスト」と、先に OPTIONS メソッドで確認する「プリフライトリクエスト」に分かれます。MDNが示す単純リクエストの主な条件は次のとおりです。
単純リクエストの主な条件
| 項目 | 条件 |
|---|---|
| メソッド | GET、HEAD、POST のどれか |
| 手動で設定するヘッダー | Accept、Accept-Language、Content-Language、Content-Type、Range(単一の範囲のみ)だけ |
Content-Type | application/x-www-form-urlencoded、multipart/form-data、text/plain のどれか |
JSONを Content-Type: application/json で送るリクエストや、Authorization などの独自ヘッダーを付けたリクエストは、単純リクエストの条件を満たさないのでプリフライトが送られます。
プリフライトが行われる流れ
- ブラウザがOPTIONSを送るAccess-Control-Request-Method と Request-Headers で予定を伝える
- サーバーが許可内容を返すAllow-Origin、Allow-Methods、Allow-Headers
- ブラウザが許可を確認する条件に合わなければ本番のリクエストを送らない
- 本番のリクエストを送るレスポンスにも Allow-Origin が必要
プリフライトへのレスポンスの例です。Access-Control-Max-Age は、プリフライトの結果をキャッシュしてよい秒数です。MDNによると、指定しない場合の既定値は5秒で、ブラウザごとの上限を超える値を指定すると上限が優先されます。
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400
Vary: Origin
資格情報を伴うリクエストの制約
Cookieなどの資格情報は、既定ではオリジン間のリクエストで送られません。送るには、fetch() なら credentials: "include"、XMLHttpRequest なら withCredentials = true を指定します。
// フロントエンド:Cookieを付けて別オリジンのAPIを呼ぶ
const res = await fetch("https://api.example.com/me", {
credentials: "include",
});
このときサーバーは Access-Control-Allow-Credentials: true を返す必要があり、返さない場合、ブラウザはレスポンスをスクリプトに渡しません。さらにMDNは、資格情報付きのリクエストに応答するときは次のヘッダーで * を使ってはならないとしています。
資格情報付きリクエストで * を使えないヘッダー
| ヘッダー | 代わりに指定するもの |
|---|---|
Access-Control-Allow-Origin | 明示したオリジン |
Access-Control-Allow-Headers | ヘッダー名の一覧 |
Access-Control-Allow-Methods | メソッド名の一覧 |
Access-Control-Expose-Headers | ヘッダー名の一覧 |
この制約があるため、「どのオリジンからでもCookie付きで読めるようにしたい」と考えて、受け取った Origin をそのまま返す実装が生まれがちです。これが次に挙げる危ない設定の代表です。
危ないCORS設定
OWASP Top 10:2025のA01「Broken Access Control」は、CORSの設定ミスによって、許可されていない、または信頼できないオリジンからAPIにアクセスできてしまうことを例に挙げています。PortSwiggerのWeb Security Academyは、よくある設定ミスとして次を挙げています。
よくあるCORSの設定ミス
| 設定ミス | 何が起きるか |
|---|---|
Origin をそのまま Access-Control-Allow-Origin に返す | どのドメインからでもレスポンスを読める |
null オリジンを許可する | サンドボックス化したiframeなどから null を送られ、許可を満たされる |
| 前方一致・後方一致・正規表現での判定ミス | 攻撃者が取得した似たドメインが許可される |
| XSSのあるサブドメインやHTTPのオリジンを信頼する | そのオリジンを足がかりに認証付きで読み取られる |
社内向けサイトで * を返す | 社外のページが利用者のブラウザ経由で社内の情報を読む |
たとえば、Cookieで認証するAPIが次のように応答していると、攻撃者のサイトに誘導されたログイン中の利用者のブラウザから、そのAPIのレスポンスを読み取られます。
// 危険な例:受け取ったOriginをそのまま返している
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
res.setHeader("Access-Control-Allow-Credentials", "true");
next();
});
文字列の一部だけを確かめる判定も同じように危険です。origin.endsWith("example.com") は attacker-example.com を通し、origin.startsWith("https://example.com") は https://example.com.attacker.net を通します。
安全な設定の書き方
許可するオリジンを一覧で持ち、受け取った Origin と完全一致したときだけ返します。MDNは、オリジンによって返す値を変える場合は Vary: Origin を付けるよう説明しています。付けないと、キャッシュが別のオリジン向けのレスポンスを返してしまうおそれがあります。
// 安全な例:許可リストと完全一致したオリジンだけを返す
const allowedOrigins = new Set([
"https://app.example.com",
"https://admin.example.com",
]);
app.use((req, res, next) => {
const origin = req.headers.origin;
res.append("Vary", "Origin");
if (origin && allowedOrigins.has(origin)) {
res.setHeader("Access-Control-Allow-Origin", origin);
res.setHeader("Access-Control-Allow-Credentials", "true");
}
if (req.method === "OPTIONS") {
res.setHeader("Access-Control-Allow-Methods", "GET, POST");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
return res.sendStatus(204);
}
next();
});
CORSが本当に必要か確かめる
OWASP Top 10:2025のA01は、対策としてCORSの利用を最小限にすることを挙げています。同じオリジンにAPIを置ける構成なら、CORSを使わずに済みます。
許可するオリジンを一覧にする
OWASPは、
Access-Control-Allow-Originには信頼できる特定のドメインだけを指定し、*や受け取ったオリジンの反射を使わないよう勧めています。nullも許可しません。完全一致で判定し、Vary: Origin を付ける
前方一致や後方一致ではなく、スキームとポートを含めた文字列全体で比べます。HTTPSのサイトでHTTPのオリジンを許可しないことも大切です。
サーバー側の認証と認可を残す
CORSはブラウザの動作を決めるだけです。
Originヘッダーはブラウザ以外からなら自由に偽れるので、OWASPはOriginだけをアクセス制御の根拠にしないよう求めています。
よくある質問
CORSエラーはサーバー側とブラウザ側のどちらの問題ですか?
多くはサーバーが返すヘッダーの問題です。ブラウザがレスポンスを受け取ったうえで、ヘッダーの条件に合わないとスクリプトへの受け渡しを止めています。MDNによると、セキュリティ上の理由でエラーの詳細はJavaScriptからは分からず、ブラウザの開発者ツールのコンソールにだけ表示されます。
CORSを設定すればCSRFを防げますか?
防げません。単純リクエストはプリフライトなしで送られ、サーバーで処理されます。CORSは「レスポンスを読めるか」を決めるだけなので、CSRFトークンやCookieの SameSite 属性などの対策が別に必要です。
開発中だけ * やオリジンの反射を使ってもよいですか?
設定がそのまま本番に残る事故を避けるため、開発環境でも許可するオリジンを一覧で持つ形にしておくのが安全です。ローカル開発用のオリジン(http://localhost:3000 など)は、開発環境の設定だけに含めます。
出典: MDN「オリジン間リソース共有 (CORS)」、OWASP HTML5 Security Cheat Sheet、OWASP Top 10:2025 A01 Broken Access Control、PortSwigger Web Security Academy「Cross-origin resource sharing (CORS)」



