CSRFとCORS: ブラウザリクエストを正しく保護する方法
クリーンなAPIを備えたWebアプリを構築したのに、ログに奇妙なリクエストが記録されていたり、さらに悪いことにセキュリティスキャナがCSRFとCORSの設定ミスを指摘してきた――そんな経験はありませんか?この2つの頭字語はよく一緒に語られますが、実際には異なる問題を解決します。それらを誤解すると、ユーザーを危険にさらしたり、正当なクロスオリジンリクエストを壊してしまう可能性があります。
この記事では、CSRFとCORSが実際に何であるかを明確にし、ブラウザセキュリティとどのように相互作用するかを説明し、機能を損なわずにアプリケーションを保護するための具体的な手順を紹介します。
CSRFとは何か、なぜ気にする必要があるのか?
クロスサイトリクエストフォージェリ(CSRF)は、ユーザーが認証済みのサイトに対して、ブラウザにリクエストを送信させる攻撃です。例えば、bank.comの銀行にログインしているとします。その後、<img src="https://bank.com/transfer?to=attacker&amount=1000">のような画像タグを含む悪意のあるサイトを訪問します。ブラウザは自動的に銀行のCookieをリクエストに含め、銀行にCSRF対策がなければ、送金が実行されてしまいます。
重要なポイントは、CSRFがサイトのユーザーブラウザに対する信頼を悪用する点です。攻撃者はセッションを盗む必要はなく、ユーザーの代わりにブラウザにリクエストを送らせるだけで済みます。
CSRF攻撃の仕組み
CSRF攻撃が成功するには、3つの条件を満たす必要があります:
- 被害者が認証済みであること(例:有効なセッションCookieを持っている)。
- 攻撃者がリクエストの構造(エンドポイント、パラメータ)を知っていること。
- リクエストに攻撃者が推測できない予測不可能なパラメータが含まれていないこと。
一般的な標的は、状態を変更する操作です:メールアドレスの変更、資金の送金、コンテンツの投稿、権限の変更などです。
CORSとは何か、なぜ存在するのか?
クロスオリジンリソースシェアリング(CORS)は、Webページがそのページを提供したドメインとは異なるドメインにリクエストを送信することを許可または拒否するブラウザのメカニズムです。これは同一生成元ポリシー(SOP)の拡張であり、SOPはあるオリジンから読み込まれたドキュメントやスクリプトが別のオリジンのリソースとどのように相互作用できるかを制限します。
CORSがなければ、悪意のあるサイトは、あなたがログインしている場合にJavaScriptを使って銀行のAPIからデータを読み取ることができてしまいます。CORSは、サーバーが特定のクロスオリジンリクエストを明示的に許可する方法を提供します。
同一生成元ポリシー(SOP)
2つのURLは、プロトコル、ホスト、ポートが同一であれば同じオリジンを持ちます。例えば、https://example.com/appとhttps://example.com/apiは同じオリジンを共有しますが、http://example.com(異なるプロトコル)やhttps://api.example.com(異なるホスト)は共有しません。
SOPは、あるオリジンのスクリプトが別のオリジンからのレスポンスを読み取ることを防ぎます。CORSは、リクエストを許可するかどうかをブラウザに伝えるHTTPヘッダーを追加することで、これを緩和します。
CSRF vs CORS: 主な違い
CSRFとCORSは対立するものではなく、異なるセキュリティ上の懸念に対処していることを理解することが重要です。CSRFは不正な状態変更リクエストを防ぐことに関わり、CORSはどのオリジンがレスポンスを読み取れるかを制御することに関わります。
| 側面 | CSRF | CORS |
|---|---|---|
| 主な目的 | 偽造リクエストの防止 | クロスオリジン読み取りの制御 |
| 攻撃ベクトル | 悪意のあるサイトがリクエストをトリガー | 悪意のあるサイトがレスポンスを読み取る |
| 防御メカニズム | トークン、SameSite Cookie | HTTPヘッダー(Access-Control-*) |
| ブラウザによる強制 | なし(サーバーが検証する必要がある) | あり(ブラウザが読み取りをブロック) |
CSRFから防御する方法
CSRFを軽減するための実証済みの戦略がいくつかあります。すべてを必要とするわけではありませんが、防御を重ねることが賢明です。
1. アンチCSRFトークンを使用する
最も堅牢な防御は、状態を変更する各リクエストに一意で予測不可能なトークンを含めることです。サーバーは処理前にトークンを検証します。トークンはユーザーのセッションに紐付け、URLに公開しないようにする必要があります(リファラーヘッダーを介した漏洩を避けるため)。
// 例: Node.js/ExpressでCSRFトークンを生成および検証する
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.get('/form', csrfProtection, (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
app.post('/process', csrfProtection, (req, res) => {
// トークンは自動的に検証される
res.send('OK');
});
2. SameSite Cookieを設定する
最新のブラウザはCookieのSameSite属性をサポートしています。LaxまたはStrictに設定すると、ブラウザがクロスサイトリクエストでCookieを送信しなくなり、多くのCSRF攻撃をブロックできます。Laxはトップレベルのナビゲーション(例:リンクのクリック)でCookieを許可しますが、Strictはすべてのクロスサイトリクエストをブロックします。
Set-Cookie: sessionId=abc123; SameSite=Lax; Secure; HttpOnly
3. OriginヘッダーとRefererヘッダーを検証する
受信リクエストのOriginまたはRefererヘッダーを確認します。期待するドメインと一致しない場合は、リクエストを拒否します。これはシンプルですが効果的な追加の防御層です。
4. AJAXにカスタムヘッダーを使用する
APIがJavaScript経由で利用される場合、X-Requested-Withのようなカスタムヘッダーを要求します。ブラウザはカスタムヘッダーに対してCORSプリフライトを強制するため、悪意のあるサイトからの単純なフォームPOSTには含まれません。
CORSを適切に設定する
CORSの設定ミスもセキュリティ問題につながる可能性があります。最も一般的な間違いは、認証情報を許可しながらAccess-Control-Allow-Origin: *を設定することです。この組み合わせはブラウザによって禁止されていますが、開発者が安全でない方法で回避しようとすることがあります。
CORSヘッダーのベストプラクティス
- 認証情報が関与する場合は、ワイルドカードではなく正確なオリジンを指定する。
- 許可するメソッドをAPIがサポートするものだけに制限する(例:
GET, POST)。 - 許可するヘッダーをアプリが実際に使用するものだけに制限する。
Access-Control-Max-Ageを設定してプリフライトレスポンスをキャッシュし、オーバーヘッドを削減する。Originヘッダーを盲目的に反映するのを避け、ホワイトリストに対して検証する。
# 例: CORS用のNginx設定
location /api/ {
if ($http_origin ~* (https://(app|admin)\.example\.com)) {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
}
if ($request_method = 'OPTIONS') {
return 204;
}
}
CORSはサーバーではなくブラウザによって強制されることを覚えておいてください。これは認証や認可の代替ではなく、同一生成元ポリシーを安全に緩和する方法です。
すべてをまとめる
ブラウザリクエストを保護するには、多層的なアプローチが必要です。以下は簡単なチェックリストです:
- アンチCSRFトークンを使用する:すべての状態変更操作に対して。
- SameSite Cookieを設定する:
LaxまたはStrictに。 - Origin/Refererヘッダーを検証する:サーバー側で。
- CORSを正確に設定する:オリジンをホワイトリスト化し、メソッドを制限し、認証情報とワイルドカードの併用を避ける。
- 定期的にテストする:セキュリティスキャナと手動チェックでアプリを検証する。
CSRFとCORSの異なる役割を理解することで、一般的な落とし穴を避け、より安全なWebアプリケーションを構築できます。
FAQ
CORSはCSRF攻撃を防ぐことができますか?
いいえ、CORSはCSRFを防ぎません。CSRF攻撃はレスポンスの読み取りに依存せず、単にリクエストをトリガーするだけです。CORSはどのオリジンがレスポンスを読み取れるかを制御しますが、リクエストの送信をブロックするわけではありません。CSRFを防ぐには、トークン、SameSite Cookie、またはオリジン検証が必要です。
Access-Control-Allow-Originを'*'に設定するのは安全ですか?
'*'に設定するのは、APIが認証情報(Cookie、HTTP認証)を使用しない場合にのみ安全です。認証情報が関与する場合、ブラウザはレスポンスを拒否します。認証済みAPIでは、常に正確なオリジンを指定してください。
AuthorizationヘッダーでJWTを使用する場合、CSRF保護は必要ですか?
JWTをCookieに保存する場合、Cookieが自動的に送信されるため、CSRF保護は依然として必要です。JWTをメモリに保存し、Authorizationヘッダーで送信する場合、攻撃者はクロスオリジンでカスタムヘッダーを設定できないため、CSRFは懸念事項ではありません。ただし、トークンを安全に保つためにXSSから保護する必要があります。
APIのCORSヘッダーをテストする準備はできましたか?Nginx Log Analyzerを使用して、リクエストパターンを検査し、疑わしいクロスオリジンの試行を発見しましょう。