1. CSRF が成立する3つの条件 #three-conditions
- 状態を変更する処理がある — メール変更、パスワード変更、送金、投稿削除など
- Cookie だけで認証している — リクエストに含まれる情報が Cookie だけで完結
- リクエストパラメータが予測可能 — 攻撃者が必要なパラメータを全て事前に把握できる
一見当たり前に見えますが、3 が重要。例えば、メール変更フォームに「現在のパスワードを再入力」が必須なら、攻撃者はそのパスワードを知らないので CSRF は成立しない。CSRF Token も同じく「攻撃者には予測不可能な値」を必須にすることで攻撃を防ぐ。
2. 典型的な攻撃フロー #attack-flow
- 被害者がうつろい EC にログインしてマイページを開く(Cookie が保存される)
- 被害者が別のタブで攻撃者の罠サイト(beauty-news.evil)を開く
- 罠サイトには
<form>+ 自動 submit() が仕込まれている - 被害者のブラウザは罠サイトの指示に従い、shop.utsuroi.example.com へ POST
- その POST には うつろい EC のセッション Cookie が自動添付されている
- サーバは Cookie を見て「ログイン済みのユーザだ」と判断、メールアドレスを変更
- 被害者は何が起きたか気づかない
被害者はクリック1回(罠サイトを開く)でなりすまし操作が完了します。罠サイトの URL を踏ませる方法はフィッシングメール、SNS、URL 短縮サービス、悪意ある広告など。
3. CSRF Token による防御 #token-defense
CSRF Token は、サーバが各セッションごとに生成する予測不可能なランダム値。フォームの hidden field として埋め込み、サーバ側で送信時に検証する。
// サーバがレスポンスとして返すフォーム
<form action="/account/email" method="POST">
<input type="hidden" name="csrf_token" value="a8x9k2m7p3q4r5s6">
<input type="email" name="email">
<button type="submit">変更</button>
</form>
// サーバ側の検証ロジック
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
abort(403);
}
攻撃者は被害者のセッションごとに生成された Token を知る術がない(Same-Origin Policy で攻撃者の JavaScript からは正規ページのレスポンスを読めない)。なので Token 検証で 403 になり攻撃失敗。
モダンな Web フレームワーク(Django、Rails、Laravel、Spring Security)は CSRF Token を標準機能として提供しています。アプリ実装者が明示的に無効化しない限り、自動的に保護されます。「自前で実装する必要がある」場合は、フレームワークの機能を確認するのが先。
4. SameSite Cookie による防御 #samesite-defense
詳細は 前章 §5 SameSite 参照。サーバが Set-Cookie: session=...; SameSite=Lax または SameSite=Strict を返すと、ブラウザが cross-site のフォーム POST 等で Cookie を送らないようになります。
Chrome 80(2020年)以降は SameSite 未指定時のデフォルトが Lax になったため、明示的に SameSite=None を指定しない限り、古典的 CSRF は自動的に防がれます。
ただし注意点:
- 古いブラウザ(Chrome 80 以前、IE 11)では適用されない
- SameSite=Lax は「トップレベルナビゲーション(リンククリック等)」では Cookie を送る → GET で状態変更するアンチパターンは依然脆弱
- サブドメイン間は same-site とみなされる(例: shop.utsuroi.com と blog.utsuroi.com)→ 内部 XSS 等で部分的に攻撃可能
- 「最後の防衛線」ではなく、Token と併用する設計が推奨
5. Referer 検証による防御 #referer-defense
HTTP の Referer ヘッダ(綴りミスで歴史的にこのまま)は、リクエスト元のページの URL を示します。サーバ側で「Referer が自サイト以外なら拒否」とすれば、cross-site からのリクエストを弾けます。
// サーバ側の検証
$referer = $_SERVER['HTTP_REFERER'] ?? '';
if (!str_starts_with($referer, 'https://shop.utsuroi.example.com/')) {
abort(403);
}
Referer はブラウザが付ける情報で、プライバシー設定で送信されない場合がある(空または欠落)。「Referer が空ならどうするか」の判断が肝心:
・空でも許可 → 攻撃者がプライバシー機能を悪用して回避可能
・空なら拒否 → 正規ユーザの一部が使えなくなる
また、サブドメイン判定や URL の部分一致のミス(shop.utsuroi.example.com.evil.example を許してしまう等)も典型的な実装ミス。Referer 検証は補助的対策で、Token を主とすべきです。
6. Double Submit Cookie パターン #double-submit
通常の CSRF Token はサーバ側でセッションに保存しますが、Double Submit Cookie パターンでは Token を Cookie とフォーム両方で送信し、サーバはそれが一致するかを検証します。
- サーバが
Set-Cookie: csrf_token=a8x9k2...; HttpOnly=falseを発行(JS から読めるようにする) - クライアント JS が Cookie から token を読み、フォームの hidden field にセット
- 送信時、Cookie とフォームの値の一致をサーバが確認
攻撃者の罠サイトは Cookie を読めない(Same-Origin Policy)ので、フォームに正しい値を入れられず攻撃失敗。サーバ側でセッションストレージに Token を保存する必要がない利点があります。
SPA や API ファーストの設計でよく使われるパターン。SameSite と組み合わせて使うとさらに堅牢。