CSRF保護とCORSを両方使う理由は何ですか?
(smagin.fyi)- Cross-Site Request について考えているうちに、CSRF保護とCORSの両方が必要だということが最初は理解できなかった。しかし、これを説明するには多くの言葉が必要になる
CSRFとCORS
- CSRF (Cross-Site Request Forgery)
- 以前はよく見られたが、現在はほとんどのWebフレームワークがデフォルトで保護機能を提供しているため、ほぼ問題にならない
- 攻撃方法: ユーザーに悪意のあるサイトで特定のフォームをクリックさせ、クロスサイトリクエストを送信するよう誘導する
- 防御方法: リクエストが他サイトから流入したものではないかを確認する
- CORS (Cross-Origin Resource Sharing)
- HTTP仕様の一部であり、特定のクロスサイトリクエストを許可する方法を定義する
- プリフライトリクエストとレスポンスヘッダーを使って、どのオリジンからリクエストを送れるかを指定する
では、クロスサイトリクエストはデフォルトで許可されていてCSRF保護が必要なのか、それともデフォルトでブロックされていてCORSによって許可する必要があるのか? 答えは 両方 である。
基本的な動作
- 同一オリジンポリシー (Same-origin policy)
- ブラウザが強制するセキュリティポリシー
- 一般に クロスサイト書き込み (Write) は許可 され、読み取り (Read) は禁止 される
- たとえば、ブラウザはフォームによるPOSTリクエストは許可するが、レスポンスを読むことはできない
- SameSite Cookieポリシー
- 2019年にCookieのデフォルト動作が変更された
- 以前はクロスサイトリクエストでもCookieは常に送信されていた
- 新しい
SameSite属性が追加され、デフォルト値がLaxに変更された - 2025年時点で、96%のブラウザが
SameSite属性をサポート し、75%が新しいデフォルト値 (Lax) をサポート している - しかしSafariはこれをデフォルト値として適用しておらず、UCBrowserも依然としてサポートしていない
- サイト (Site) とオリジン (Origin) の違い
- オリジン (Origin):
プロトコル + ホスト名 + ポートの組み合わせ - サイト (Site):
プロトコル + トップレベルドメイン + 1の組み合わせ(サブドメインとポートは無視される)
- オリジン (Origin):
CORS
- CORSは同一オリジンポリシーに対して、特定のオリジンに限って例外的に許可する仕組み である
- ブラウザはリクエストを送る前に
OPTIONSタイプの プリフライトリクエスト を送信する - サーバーはレスポンスヘッダーを通じて許可ルールを定義する(
Access-Control-*ヘッダーを使用) - CORSが適用されるリクエストの種類:
fetchおよびXMLHttpRequest- Webフォント
- WebGLテクスチャ
canvasでdrawImageにより描画された画像/動画フレーム- CSS
shape-outsideプロパティで使用される画像
- ただし、フォーム送信には例外的にCORSが適用されない
- HTML 4.0 の
<form>タグは昔からクロスサイトリクエストを許可していた - したがって既存のサーバーは、すでにCSRF攻撃を防御するよう設計されている必要があった
- サーバーがレスポンスを共有するには
Access-Control-Allow-Originを設定する必要があるが、リクエスト自体はプリフライトなしでも受け入れられる
- HTML 4.0 の
質問:
SameSiteポリシーとこの方式は、どのように一貫性を保っているのか?
CSRF保護の方法
- クロスサイト書き込みリクエストは許可されるが、レスポンスは共有されない
- ほとんどのWebサイトでは、クロスサイト書き込みを許可したくない
- 標準的なCSRF防御方法
- ユーザーごとの CSRFトークン をリクエストに含めて検証する
- 方法:
- フォーム送信: hidden inputとしてトークンを追加
- JSリクエスト: Cookieまたは
metaタグに保存し、リクエストヘッダーやパラメータに含める
- JSリクエストはもともとクロスサイトではデフォルトでブロックされる
- ただし同一サイトリクエスト (same-site request) では許可される
- CSRFトークンを含めれば、すべてのリクエストを同じ方法で検証できる
- 追加のセキュリティ上の利点
- ブラウザがデフォルトでレスポンスの読み取りをブロックするという前提の上で機能する
Originヘッダーを検査するよりも安全性が高い
質問: 一部のフレームワークではCSRFトークンを定期的に変更するが、その理由は何か?
ブラウザの役割
- Webセキュリティの核心は ブラウザを信頼できるかどうか にかかっている
- ブラウザは次のことを行う:
- 同一オリジンポリシーを強制する
- レスポンスが許可されていない場合は読み取れないようにブロックする
SameSite=Laxのデフォルト値を適用するかどうかを決定する- CORSを実装し、安全なプリフライトリクエストを送る
私たちは利用しているブラウザを信頼しなければならない。
結論
SameSite=Laxが100%のブラウザでサポートされれば、セキュリティはさらに強化されるだろうが、
現状では依然としてクロスサイト POSTリクエストだけが例外的に許可 されている- そのため開発者は、引き続きCSRF保護を考慮する必要がある
「インターネットはますます安全になっているが、その分だけ過去との互換性も次第に失われている。」
1件のコメント
Hacker Newsのコメント
CORSは、サーバーがブラウザに対して、どのクロスオリジンリクエストがレスポンスを読み取れるかを明示的に伝える仕組みである
CSRF保護は、認証済みユーザーになりすました悪意あるクロスオリジンリクエストが、無断で操作を実行することを防ぐ
CSRF保護は書き込み保護に関するものであり、CORSは読み取り保護に関するものである
JSから開始されたリクエストは、デフォルトではクロスサイトでは許可されない
このトピックについては、より良い説明があると思う
ブログ記事の質問への応答
2022年にMDNのCORS記事へ、「シンプルリクエスト」という用語の由来を明確にするための段落を追加した
SameSite が CORS のプリフライトとは独立して追加されたことが混乱を招く
csrf を使わなくても安全だと考えるかもしれないが、一部のライブラリ(例: django rest framework)は Content-Type ヘッダーが設定されていれば HTML フォームを処理できる
CSRFトークンがローテーションされる理由についての質問
複雑なトピックについてのフローチャートを求める声
こうした仕組みは、簡単な診断トレースを支援してくれない
CORSが登場する前から、ページのオリジンではない任意のエンドポイントにリクエストを送れていたのに、なぜレスポンスは見られなかったのか理解できない
CSRF保護に関する混乱