TL

CSRFの成立条件と多層防御の原理

勝手に送金・設定変更されるCSRFの正体を、なぜブラウザが他サイト発のリクエストにCookieを付けて送るのかから解明。各防御の効きどころを見極め、堅い設計の判断軸が手に入ります。

応用CSRFセキュリティCookieSameSiteWeb最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.CSRF はブラウザが「リクエストの宛先ドメインのCookieを自動付与する」性質を悪用する攻撃。攻撃者は中身を読めず、認証済みの副作用だけを起こす。
  • 2.成立には3条件(Cookieベースの認証/推測可能なリクエスト/攻撃者がレスポンスを読む必要がない)が揃うことが必要。1つでも崩せば防げる。
  • 3.防御は SameSite Cookie・Origin/Referer検証・トークン同期/二重送信Cookie の多層で組む。効きどころと前提が各々違い、単独では穴が残る。

CSRF(Cross-Site Request Forgery)は「攻撃者のサイトから、被害者の認証情報を使って、別サイトへ副作用のあるリクエストを飛ばす」攻撃です。鍵は、攻撃者がレスポンスを読む必要が一切ないこと。送金や設定変更が「実行されさえすれば勝ち」という構造が、CORSやSame-Origin Policyでは防ぎきれない理由になっています。

なぜ他サイト発のリクエストにCookieが付くのか

ブラウザのCookie送信ルールは「宛先(リクエスト先)のドメインに紐づくCookieを自動付与する」というものです。リクエストの発信元がどこかは、従来は問われませんでした。つまり攻撃者のページ evil.example に置かれたフォームが bank.example へ POST すると、ブラウザは律儀に bank.example のセッションCookieを添えて送ります。

この自動付与こそがCSRFの土台です。攻撃者はCookieの値を知る必要がありません。読めなくても、ブラウザが勝手に正しいCookieを付けてくれるからです。

<!-- evil.example に置かれた罠。被害者が開くだけで送金が走る -->
<form action="https://bank.example/transfer" method="POST" id="f">
  <input type="hidden" name="to" value="attacker">
  <input type="hidden" name="amount" value="100000">
</form>
<script>document.getElementById('f').submit();</script>
Same-Origin Policy も CORS も止められない

SOP / CORS が制限するのは「レスポンスの中身を JS に読ませるか」です。一方 CSRF は副作用(状態変更)だけが目的で、レスポンスを読みません。フォーム送信や画像読み込みによるリクエストの送出そのものは、これらの仕組みでは止まらないのがポイントです。読ませない防御と、送らせない防御は別物だと切り分けてください。

成立の3条件

CSRF が刺さるには、次の3つが同時に成り立つ必要があります。逆に言えば、どれか1つでも崩せば成立しません。

条件内容崩すと…
Cookieベース認証認証状態がCookieで保持され自動付与されるAuthorizationヘッダ等にすれば自動付与されない
リクエストの推測可能性攻撃者がパラメータを事前に組み立てられる予測不能なトークンを必須にすると組み立て不能
レスポンス不要副作用が起きれば目的達成(読む必要なし)—(攻撃の性質なので崩せない)

3つ目は攻撃の本質なので除去できません。だから現実の防御は、上の2つ(自動付与される認証情報/推測可能なリクエスト)のいずれかを成立させないことに集約されます。

トークン同期(Synchronizer Token)

最も古典的かつ堅い防御です。サーバーがセッションごとにランダムなトークンを生成・保持し、フォームの hidden フィールドに埋め込みます。送信時にサーバーは「セッションに紐づくトークン」と「リクエストに付いてきたトークン」を比較し、一致しなければ拒否します。

効く理由は、攻撃者がトークン値を知り得ないからです。トークンはレスポンスHTMLの中にあり、攻撃者は被害者のレスポンスを読めません(SOPが効く)。よって正しい hidden 値を埋めたフォームを攻撃者は構築できず、条件②(推測可能性)が崩れます。

1. GET /form → サーバーが token=Q7x... を生成しsessionに保存、HTMLに埋込
2. POST /transfer (token=Q7x... を含む)
3. サーバー: session.token == body.token か検証 → 不一致なら 403
サーバー側に状態を持つのが特徴

同期トークンは「サーバーがトークンを保持して照合する」点が肝です。セッションストアに状態を持つため、ステートレスにしたい構成では次の二重送信Cookieが選ばれます。SPA では meta タグや初回レスポンスでトークンを配り、以降のリクエストにカスタムヘッダで載せる形が一般的です。

二重送信Cookie(Double Submit Cookie)

サーバーに状態を持たせない方式です。ランダムなトークンを Cookie とリクエスト本体(hidden もしくはカスタムヘッダ)の両方に入れ、サーバーは「2つの値が一致するか」だけを見ます。セッションストアへの照合が不要なのでステートレスに組めます。

効く理屈は、攻撃者は Cookie の値を読めない(SOPで遮断)うえ、別ドメインから bank.example の Cookie を任意の値に書き換えることもできないから、Cookie 側とボディ側を一致させられない、というものです。

素朴な二重送信には既知の弱点がある

二重送信Cookie は「Cookie を攻撃者が制御できない」前提に依存します。しかしサブドメインの脆弱性などで攻撃者が example ドメインに Cookie を**注入(cookie tossing)**できると、自分が知る値を Cookie とボディの双方に揃えられ、防御が破れます。対策として、トークンをセッションに署名付きで束縛する(HMAC でセッション識別子と結合する)署名付き二重送信が推奨されます。単なる「2値が一致」では不十分です。

Origin / Referer ヘッダ検証

ブラウザが付ける Origin(および Referer)はリクエストの発信元を示します。サーバー側で「発信元が自分のオリジンか」を確認すれば、evil.example 発の POST を弾けます。条件②ではなく「発信元の素性」を直接見る防御です。

Origin は CSRF に効きやすいヘッダです。クロスサイトの POST には付与され、JS から偽装できません(fetch でも Origin はブラウザが上書きするユーザー制御不可ヘッダ)。

観点OriginReferer
含む情報scheme + host + port のみフルURL(パス・クエリ含む)
欠落の起こりやすさ比較的安定して付くプライバシー設定等で省略され得る
検証方針許可オリジンと完全一致を要求存在時のみ host を検証(無ければ別防御に委ねる)
“ヘッダが無ければ通す”は禁物

Origin/Referer 検証の落とし穴は、ヘッダが欠落したときの扱いです。「無ければ素通し」にすると、ヘッダを落とせる経路で回避されます。原則は fail-closed(判断材料が無ければ拒否)。ただし正規の同一オリジン GET 等で欠落するケースもあるため、状態変更リクエスト(POST/PUT/DELETE 等)に限って厳格化し、トークン方式と併用するのが堅実です。

SameSite Cookie

そもそも条件①(クロスサイトでCookieが自動付与される)をブラウザ側で断つのが SameSite 属性です。Cookie 自体に「別サイト発のリクエストには付けるな」と指示します。

クロスサイトでの送信効きどころ・注意
Strict一切送らない外部リンクから戻った直後も未ログイン扱いになりUXに影響
LaxトップレベルGETナビゲーションのみ送る多くのブラウザの既定。フォームPOSTには付かずCSRFを大幅に抑止
None常に送る(Secure必須)クロスサイト用途で明示。CSRF保護は別途必要

既定が Lax になったことで、フォーム POST のようなクロスサイトの副作用リクエストには Cookie が付かなくなり、典型的なCSRFの多くが自動的に無力化されました。ただし Lax は「トップレベル GET ナビゲーション」には Cookie を送るため、状態変更を GET で実装していると穴になります。状態変更は必ず POST 等にすること。

SameSite 単独に依存しない

SameSite=Lax は強力ですが万能ではありません。(1)「サイト」は eTLD+1 単位なので、同一サイト内のサブドメインからの攻撃(サブドメイン乗っ取り等)は防げません。(2) クライアントが古い、または属性が正しく付いていないと無効化されます。(3) None を選ばざるを得ない構成では保護が外れます。SameSite は土台として敷きつつ、トークンや Origin 検証を重ねる多層防御が前提です。

多層で組む

各防御は前提と効きどころが異なります。実務では複数を重ね、どれか1つが破れても全体が崩れないようにします。

防御崩す条件主な弱点・前提
SameSite=Lax①Cookie自動付与サブドメイン経由/None指定/GETでの状態変更に弱い
Origin/Referer検証発信元の素性ヘッダ欠落時の扱い/同一サイト内攻撃
同期トークン②推測可能性サーバー状態が必要/トークン配布設計
署名付き二重送信②推測可能性ステートレス可だがcookie tossing対策に署名必須

推奨の重ね方は、(a) すべての認証 Cookie に SameSite=Lax(最低限)+ SecureHttpOnly を付ける、(b) 状態変更は必ず非 GET にする、(c) その上で同期トークンまたは署名付き二重送信を必須化、(d) Origin 検証を fail-closed で追加、という構成です。

なお、JS から読み書きするトークン方式は XSS に弱い点に注意してください。XSS があればトークンは漏れ、CSRF 防御は丸ごと迂回されます。CSRF 対策と並行して、入力のサニタイズや Content Security Policyの内部動作と回避耐性 による XSS 抑止を組み合わせるのが本筋です。

土台となる認証情報の運び方は Cookie とセッションCookie・セッション・JWT(Web認証) を、なぜ「読む防御」と「送る防御」が別なのかは 同一オリジンポリシーとサイト分離の信頼境界 もあわせてどうぞ。

Web/フロントエンド Article

CSRFの成立条件と多層防御の原理を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

CSRF

比較で見る軸

難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 5

導入後に効く点

成立には3条件(Cookieベースの認証/推測可能なリクエスト/攻撃者がレスポンスを読む必要がない)が揃うことが必要。1つでも崩せば防げる。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
Web/フロントエンド
タグ数
5

判断チェックリスト

  • 自社の用途が「CSRF / セキュリティ」に近いか確認する。
  • 強みである「CSRF はブラウザが「リクエストの宛先ドメインのCookieを自動付与する」性質を悪用する攻撃。攻撃者は中身を読めず、認証済みの副作用だけを起こす。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

CSRFセキュリティCookieSameSiteWebCSRFセキュリティCookie