CSRFトークンとは?
CSRF トークンは、CSRF(クロスサイトリクエストフォージェリ)というサイバー攻撃を防ぐために、Webサーバーが生成する「一意で予測不可能な秘密の値」です。
🚨 CSRF(クロスサイトリクエストフォージェリ)攻撃とは
ユーザーが特定のサイト(例:銀行やSNS)にログインしている状態のまま、悪意のある別サイトを閲覧した際に、ユーザーが気づかないうちに(意図しない)重要なリクエスト(資金送金、パスワード変更、アカウント削除など)を、ログイン済みのサーバーへと強制的に送らされてしまう攻撃です。
🔑 CSRFトークンの仕組み(3ステップ)
-
ステップ1. ページ表示時(サーバーの処理)
ユーザーが正規のフォーム画面を開いたとき、サーバーはランダムなトークンを生成し、サーバー側の「セッション」に保存します。同時に、HTMLのフォーム内(
input type="hidden")にそのトークンを埋め込んでユーザーに返します。 -
ステップ2. フォーム送信時(ブラウザの処理)
ユーザーがボタンを押してフォームを送信すると、フォーム内に埋め込まれた「CSRFトークン」が送信されます。このとき、ブラウザはログイン情報(セッションIDが入ったCookie)を自動的にセットして一緒に送信します。
-
ステップ3. サーバーでの検証
サーバーは、届いた「リクエスト内のトークン」と「セッションに保存してあるトークン」を比較します。
- 一致すれば: 正規の画面から送られたリクエストとみなして処理を許可。
- 不一致・または無し: 不正なリクエストとみなして拒否。
🏠 ネットバンキングで見る具体的な例え話
あなたが銀行のネットバンキングにログインしているとします。
1. CSRFトークンがない場合(攻撃が成立してしまう)
あなたがログインしたまま、たまたま悪意のある別のサイト(罠サイト)を開いてしまいました。
その罠サイトには、開いた瞬間に裏で勝手に以下のようなリクエストを「銀行のサーバー」に送るスクリプトが仕込まれていました。
POST https://銀行.com/送金
{
"相手口座": "攻撃者の口座",
"金額": "100,000円"
}
あなたのブラウザは、銀行にログイン中であることを示す「Cookie(セッション情報)」を持っています。ブラウザは、宛先が「銀行.com」であれば、罠サイトからのリクエストであっても自動的にCookieを添付してしまいます。
銀行サーバーはCookieを見て「お、ログイン中のあなたからの依頼だな」と信じ込み、勝手に送金を実行してしまいます。
2. CSRFトークンがある場合(安全!)
銀行サイトがCSRFトークンを導入している場合、正規の送金画面には以下のような隠しコードが入っています。
<form action="/送金" method="POST">
<input type="hidden" name="csrf_token" value="a7f3k9m2x5q8w1n4">
<input type="text" name="相手口座">
<input type="number" name="金額">
<button>送金</button>
</form>
罠サイトは、あなたを騙して裏で勝手に「送金リクエスト」を飛ばすことはできますが、この「a7f3k9m2x5q8w1n4」というトークン値を知ることができません。
なぜなら、ブラウザには同一オリジンポリシー(SOP)というセキュリティ規則があり、悪意のあるサイト(別オリジン)のJavaScriptが、銀行サイト(自オリジン)の画面(HTML)の中身を勝手に読み取ることを禁止しているからです。
結果として、罠サイトが送るリクエストには「正しいCSRFトークン」を含めることができません。
【銀行サーバーのチェック】
■ あなたが正規の画面から送った場合
Cookie: あり(自動添付) ✓
トークン: "a7f3k9m2x5q8w1n4" (画面から送信) ✓
👉 「一致!送金を実行します」
■ 罠サイトから勝手に送られてきた場合
Cookie: あり(ブラウザが自動添付しちゃう) ✓
トークン: なし(またはデタラメな値) ✗
👉 「トークンが一致しないので拒否!」
📌 なぜ必要なのか?(まとめ)
| 項目 | 概要 |
|---|---|
| 根本的な原因 | Cookieは「誰が(どのセッションか)」を証明できますが、ブラウザの仕様上、「どのサイトからリクエストが送られたか」を区別せずに自動添付されてしまうため。 |
| トークンの役割 | フォーム内に「その都度変わる秘密の値」を混ぜることで、リクエストが「サーバーが用意した正規の画面から送られたものか」を検証する。 |
| 利用シーン | データの登録・変更・削除を伴うリクエスト(POST, PUT, PATCH, DELETEなど)に対して必須。状態を変更しない安全なリクエスト(GET)には不要。 |