CSRFで何が起きるか
CSRFは、攻撃者のページを開いた利用者のブラウザから、ログイン中の別サイトへリクエストを送らせ、利用者が選んでいない設定変更などを実行させる攻撃です。以下ではCookieでログイン状態を確認するサービスAを例に説明します。仕組みを示す説明用の仮定で、実際の被害報告とは区別します。
本来、登録メールアドレスは、利用者がサービスAの設定画面で新しいアドレスを入力して変更します。攻撃者は、自分のアドレスをサービスAのメールアドレス変更APIへPOSTするフォームを、自分のページに用意します。フォームの送信先には別サイトのURLも指定でき、ブラウザはその宛先へ直接送信できます。そのページのJavaScriptがフォームを自動送信すると、利用者はサービスAの変更ボタンを押していなくても、ブラウザからサービスAへ変更先のアドレスが送られます。
この例では、サービスAのログイン用CookieにSameSite=None; Secureが設定され、ブラウザも別サイトからのCookie送信を許可しているとします。SameSite=Noneは別サイトからのリクエストにもCookieを付けられる設定で、SecureはHTTPS通信に送信を限定します。利用者はサービスAにログイン中で、送信先はCookieの対象ホストとパスに合うHTTPSの変更APIです。ブラウザは保存済みのログイン用Cookieを、そのHTTPリクエストのCookieヘッダーに付けます。攻撃者がCookieの値を知る必要はありません。
サービスAのサーバーは、ログイン時にセッションIDと利用者の対応を保存しています。受け取ったCookie内のIDでその保存情報を探し、どの利用者のリクエストかを特定します。APIがログイン確認だけで変更を受け付け、リクエストが正規画面から送られたかを検証しなければ、利用者の登録先を攻撃者のアドレスへ更新してしまいます。その登録先へ以後のアカウント通知を送るサービスなら、攻撃者が通知を受け取れます。利用者を特定できることだけを根拠に、攻撃者のページが送らせた変更まで受け付ける点が問題です。
正常ならどう動くか
正規のメールアドレス変更では、サービスAのサーバーが、ログイン中の利用者へ設定画面のHTMLを返します。CSRFトークンを使う構成では、サーバーが予測困難な値を作り、サーバー側のセッションIDに対応する保存領域へ記録するとともに、HTMLフォーム内の非表示の入力欄にも同じ値を入れます。非表示でもフォームの入力項目なので、送信対象になります。
利用者が変更先のアドレスを入力し、変更ボタンを押すと、ブラウザはアドレスとCSRFトークンをPOSTの本文に入れて変更APIへ送ります。それとは別に、ブラウザは対象ホスト・パス・通信方式などの条件に合うログイン用CookieをCookieヘッダーへ付けます。正規画面から同じサイトへ送る場合は、SameSiteによる別サイト向けの制限には該当しません。
サーバーは、Cookie内のセッションIDをキーに保存情報を探し、対応する利用者とトークンを取り出します。そのトークンとPOST本文のトークンを照合します。一致し、入力値や変更権限などの検査にも通った場合だけ、新しいアドレスを保存します。Cookieは利用者を特定するために使い、トークンは正規画面へ渡した値をリクエストが含むか確認するために使います。
攻撃者が自分のアカウントで設定画面を開いても、得られるのは自分のセッション用のトークンです。被害者のトークンは推測できず、同一オリジンポリシーが守られて対象サイトが読み取りを許可していなければ、攻撃者のページから被害者の設定画面の応答も読めません。ブラウザはCookieを自動送信しても、攻撃者のフォームへ被害者のトークンを自動で追加しないため、攻撃者が用意した値は照合で拒否されます。
不備と攻撃が成立する流れ
攻撃者は、対象サイトの変更APIのURLと入力項目の名前を調べ、変更先に自分のアドレスを入れたPOSTフォームを自分のページに置きます。被害者がそのページを開くと、ページのJavaScriptがフォームを送信します。送信するのは被害者のブラウザで、宛先は対象サイトの変更APIです。HTMLフォームは別サイトにも送信できるため、対象APIがフォームのデータ形式を受け付ける構成なら、変更先の値が届きます。
対象サイトのログイン用Cookieが有効で、宛先のホスト・パスに一致し、HTTPS必須などの条件も満たすと、ブラウザはさらにSameSiteの設定を確認します。別サイトからのPOSTでも、SameSite=None; Secureでブラウザ側の制限がなければCookieを付けられます。明示的なSameSite=Laxでは別サイトからのPOSTには付かず、Strictでも別サイトからのリクエストには付きません。このPOSTの例は、ログイン用Cookieが送られない構成では成立しません。
Cookieが届くと、サーバーはセッションIDに対応して保存した利用者情報を探し、被害者を特定できます。APIがCSRFトークンを要求せず、送信元も検証せずに変更を保存すれば、攻撃者が指定したアドレスが被害者の登録先になります。攻撃者が被害者のブラウザからAPIのレスポンスを読めなくても、サーバー側の保存はすでに起きています。成立には、攻撃者が必要な入力値を用意でき、被害者のアカウントにその変更権限があることも必要です。
起こり得る被害と条件
ここで挙げるのは、Cookieで利用者を特定するAPIが、攻撃者のページからのフォーム送信を受け付け、トークンや送信元を検証せずに処理する場合の説明用の例です。いずれも、被害者がログイン中で、そのAPIへのリクエストにログイン用Cookieが付く必要があります。たとえばSameSite=None; SecureのCookieを、ブラウザが別サイトからのHTTPSリクエストにも送信できる構成です。
登録メールアドレスの変更では、攻撃者のページが、攻撃者のアドレスを変更APIへ送らせます。APIがCookieで特定した被害者の登録先としてその値を保存し、以後のアカウント通知をその登録先へ送るなら、攻撃者が通知を受け取れます。新しいアドレスの保存だけでアカウントの乗っ取りまで確認されたことにはなりません。
メール転送設定では、攻撃者のページが、転送先として攻撃者のアドレスを設定APIへ送らせます。APIが被害者の転送設定として保存し、メールサーバーがその設定を使って以後の受信メールを転送するなら、攻撃者は被害者宛てのメールのコピーを入手できます。
送金では、攻撃者のページが、指定した受取先と金額を送金APIへ送らせます。APIがCookieで被害者を特定し、残高と送金権限の条件を満たすとして追加確認なしで処理すると、被害者の資金が指定先へ移ります。利用者しか入力できない再認証情報をサーバーが要求し、攻撃者が用意できなければ、この送金は受理されません。
防ぎ方と、防げる理由
フォームを使う設定変更では、まずフレームワークのCSRF保護を使い、サーバー側の検証を有効にします。セッションに保存する方式なら、サーバーは予測困難なトークンを作ってサーバー側のセッションIDに対応する保存領域と、正規画面の非表示の入力欄へ同じ値を入れます。ブラウザはフォーム送信時にトークンを本文へ入れます。APIはCookieで特定したセッションの値と照合し、欠落・不一致なら変更を保存する前に拒否します。
別サイトの攻撃者はフォームの入力欄を作れますが、被害者のセッションのトークンは推測できません。同一オリジンポリシーが守られ、対象サイトが攻撃者へ読み取りを許可していなければ、攻撃者のページから正規画面の応答を読んでトークンを取り出すこともできません。ブラウザがCookieを自動で付けても、攻撃者のフォームへ正しいトークンを自動で追加することはないため、照合に失敗します。
Originを検査する方式では、サーバーがリクエストのOriginヘッダーと、許可した正規画面のオリジンを比較します。オリジンはスキーム・ホスト・ポートの組で、ブラウザが送信元の情報として設定します。ページのJavaScriptはOriginヘッダーを任意の値に書き換えられないため、攻撃者のオリジンを拒否できます。文字列の部分一致ではなく組全体を比較し、欠落やnullを自動で許可しません。Originがない場合はRefererヘッダーも使えます。Refererには送信元ページのURLの全部または一部が入り、たとえばhttps://app.example.com/settingsなら、https://app.example.comの部分を取り出して許可値と比較します。両方のヘッダーから信頼できる送信元を確認できない場合は、有効なCSRFトークンなど別の対策で検証できたときだけ受理し、それ以外は拒否します。
JavaScriptから呼ぶAPIでは、独自ヘッダーを必須にする方法もあります。正規画面のJavaScriptがヘッダーを付け、APIは付いていないリクエストを保存前に拒否します。HTMLフォームでは独自ヘッダーを設定できません。攻撃者が別オリジンのJavaScriptから付けようとすると、ブラウザは先にOPTIONSリクエストを送り、その送信元やヘッダーをAPIが許可するか確認します。これがプリフライトです。CORSで攻撃者のオリジンを許可しなければ、ブラウザは独自ヘッダー付きの変更リクエストを送りません。信頼できないオリジンを許可すると、この対策を回避されます。
CookieのSameSiteも、認証情報が送られる経路を制限します。明示的なLaxなら別サイトからのPOSTにはログイン用Cookieが付かず、Strictなら別サイトからのリクエストには付きません。ただしLaxは、別サイトのリンクからページ全体を移動するGETにはCookieを付けるため、GETで設定を変更する実装は避けます。属性の省略時はブラウザによって既定動作が異なり、設定直後のPOSTにも送る例外があるため、明示設定で確認します。
SameSiteでは、スキームと登録可能なドメインを組にしてサイトを判定します。登録可能なドメインは、この例ではサブドメインを除いたexample.comで、ポートの違いは判定に使いません。スキームがHTTPとHTTPSで異なる場合は別サイトになります。たとえば同じHTTPSのapp.example.comと投稿用のposts.example.comは、オリジンが異なっても同じサイトとして扱われます。投稿用サブドメインを攻撃者が操作できるなら、SameSiteだけではアプリ宛てのCookie送信を止められません。トークンや送信元の検証を併用します。送金などには、利用者がその場でパスワードなどを入力する再認証も設け、攻撃者が既知の値だけで操作を完了できないようにします。
よくある誤解
POSTに変えるだけではCSRF対策になりません。攻撃者のHTMLフォームもPOSTを送信でき、ブラウザがログイン用Cookieを付け、APIがその形式を検証なしで受け付ければ変更が保存されます。GETで変更しない設計に加えて、トークンや送信元などをサーバーで検証します。
CORSで応答の読み取りを許可しなくても、HTMLフォームの送信自体は止まりません。Cookieが付いた送信をAPIが受理すれば、攻撃者が結果を読めなくても設定は変わります。一方、独自ヘッダー付きの別オリジンのリクエストでは、ブラウザが送信前にAPIへ許可を確認します。CORSが送信前の拒否に使えるのは、このプリフライトが必要なリクエストです。
XSSがあると、対象サイトの表示処理の不備によって、攻撃者のJavaScriptが対象サイトのページ内で実行されます。そのコードが画面内のCSRFトークンを読み、変更APIへ送信できる場合、サーバーのトークン検証にも通ります。CSRF対策は、このような対象サイト内でのコード実行を防ぐものではないため、XSSへの対策も必要です。
実装の確認点
設定変更・転送設定・送金など、データを更新する全APIについて、検証が保存や送金より前に実行されるか確認します。セッションに結び付けたトークン方式では、欠落、空文字、別セッションのトークンを送って拒否されることと、対象データが変化していないことを確認します。古い画面や別のデータ形式で同じAPIを呼んでも、検証を省略しないか調べます。
Origin検査では、許可していないオリジン、正規ホスト名を含むだけの別ホスト、null、ヘッダーなしを確認します。Originが使えない場合も、送信元URLを含むRefererヘッダーから取り出したオリジンや、セッションに結び付けたCSRFトークンによる検証を必ず経てから保存することを確かめます。独自ヘッダー方式なら、ヘッダーなしの送信と、信頼していないオリジンからのプリフライトが許可されないことを確認します。
自分が管理する検証環境では、ブラウザの開発者ツールで、別サイトから送ったリクエストの宛先・メソッド・Cookieヘッダーを確認します。SameSiteの明示値、ホスト・パス・HTTPSの条件、ブラウザのCookie制限をそろえて比較します。LaxでもCookieが付くページ全体のGET移動で更新が起きないこと、再認証が必要な操作が入力なしで完了しないことも確認します。
出典・確認範囲
- [1] Web Security Academy: CSRFPortSwigger / 確認日 2026-11-01
- [2] Set-Cookie headerMDN Web Docs / 確認日 2026-11-01
- [3] Cross-Site Request Forgery Prevention Cheat SheetOWASP / 確認日 2026-11-01
- [4] Referer headerMDN Web Docs / 確認日 2026-11-01
- [5] SiteMDN Web Docs / 確認日 2026-11-01