ssecutils
Security / Browser-native guide

CSRF の仕組みと対策

SameSite Cookie、CSRFトークン、Origin検証を中心に、ログイン済みユーザーを狙う攻撃を理解します。

7Zero tracking reading surface

CSRF とは何か

CSRF(Cross-Site Request Forgery)は、ログイン中のユーザーを罠サイトに誘導し、本人が意図しないリクエストを正規サービスに送らせる攻撃です。日本語では「リクエスト強要」「セッションライディング」とも呼ばれます。

鍵となるのは 「ブラウザは Cookie を勝手に付けてくれる」 という挙動です。被害者が銀行 A にログイン中(Cookie 保持)に、攻撃者の罠ページから A 宛にリクエストが飛ぶと、ブラウザはお行儀よく Cookie を付けてしまい、A から見ると「正規ユーザーの操作」に見えてしまいます。

典型的な攻撃シナリオ

被害者は銀行 bank.example.com にログイン済み。攻撃者は罠サイト evil.example に次の HTML を仕込みます:

<form action="https://bank.example.com/transfer" method="POST">
  <input name="to" value="attacker">
  <input name="amount" value="1000000">
</form>
<script>document.forms[0].submit()</script>

被害者がこの罠を踏むと、ブラウザは bank.example.com の Cookie を自動付与して送信。サーバ側は Cookie が一致するので「本人の振込指示」と判断 → 送金完了

攻撃者は レスポンスを読めない(同一オリジンポリシーで防がれる)点が重要。CSRF はあくまで「副作用のあるリクエストを発火させる」攻撃で、データを読み出す XSS とは性質が違います。

XSS との違い

観点XSSCSRF
攻撃の場正規サイトのドメイン上で JS を実行罠サイトから正規サイトへリクエスト送信
レスポンスは読める?読める(同一オリジン下)読めない(CORSで遮断)
攻撃の本質スクリプト実行意図しない副作用リクエスト
主防御出力時エスケープ + CSPSameSite Cookie + CSRFトークン

XSS が成立すると CSRF 対策(トークン等)も同オリジンから読み取られて無効化されるので、XSS の方が圧倒的に強い攻撃です。CSRF 対策は XSS が無いことを前提にした「最低限の備え」と捉えるのが現実的。

防御1: SameSite Cookie

現代の CSRF 防御の主役は SameSite 属性です。Cookie に付けることで「クロスサイト遷移時の自動付与」を制限します。

挙動用途
Strictクロスサイトでは一切送らない銀行など最高機密
Lax(既定値)トップレベル GET 遷移時のみ送る多くの一般サービス
None常に送る(要 Secureサードパーティ埋込必須の場合

Chrome は 2020 年から 既定値が Lax。これにより「外部サイトから POST で罠送信」という古典 CSRF はかなりの確率で自動防御されるようになりました。とはいえ、明示的に LaxStrict を指定し、クロスオリジン埋込が必要な Cookie だけ None; Secure にするのが安全です。

SameSite=None を使う場合は必ず Secure(HTTPS 限定)を付ける。Cookie Parser で属性可視化すると、None 単独だとブラウザに拒否される警告が出ます。

防御2: CSRF トークン(Synchronizer Token Pattern)

フォーム送信時に「サーバが発行したランダムなトークン」を hidden field で持たせ、サーバ側で照合する方式です。攻撃者の罠サイトはこのトークンを取得できないので攻撃が失敗します。

<form method="POST" action="/transfer">
  <input type="hidden" name="csrf_token" value="${session.csrf_token}">
  <input name="to" ...>
  <input name="amount" ...>
</form>

実装パターン:

  • セッション結合: トークンをセッションに紐付け(最も一般的)
  • Double Submit Cookie: Cookie とリクエストボディに同じ値を入れて照合(ステートレスで実装しやすい)
  • 署名トークン: HMAC でユーザーID等に署名し、サーバ状態を持たない

Django, Rails, Laravel, Spring 等の主要フレームワークはデフォルトでこの仕組みを内蔵しています。「自分で書かない」ことが最大の安全策

防御3: Origin / Referer ヘッダ検証

重要な操作(POST/DELETE 等)を受ける時、Origin ヘッダを見て「自サイトから来たリクエストか」を確認するシンプルな対策です。

if (req.headers.origin !== "https://bank.example.com") {
  return res.status(403).end()
}

モダンブラウザは状態変更系リクエストに Origin を必ず付けるため、SameSite Cookie + Origin チェックの組み合わせは強力。CSRF トークンとほぼ同等の効果を、状態保持なしで得られます。

防御4: カスタムリクエストヘッダ + CORS

SPA + API の構成では、API リクエストに X-Requested-With: XMLHttpRequest のようなカスタムヘッダを必須にする手があります。クロスオリジンでカスタムヘッダ付き POST はプリフライト(OPTIONS)が必要で、CORS が許可しなければそもそも本リクエストが飛びません。

さらに API を Cookie 認証ではなく Bearer トークンAuthorization: Bearer ...)にすれば、ブラウザが自動付与しないので CSRF はそもそも成立しません。SPA や JWT 認証では事実上これがベース防御になっています。

防御してはいけないやり方

  • POST にすれば安全 ❌: form 自動 submit で容易に POST できるので無意味
  • Referer 必須にする ⚠: 古いブラウザ・プライバシー設定で Referer が落ちることがあり、過剰拒否しがち。Origin の方が信頼できる
  • CAPTCHA だけで対策 ⚠: 重要操作ごとに毎回 CAPTCHA は UX 破壊。お金が絡む操作の最終確認に限定する

おわりに

CSRF は「ブラウザが律儀に Cookie を付ける」前提で成立する攻撃で、現代では SameSite Cookie + CSRF トークン or Origin チェック の二段構えが定番です。SPA + Bearer トークン構成なら原理的に成立しないので、API 設計の段階で経路を選ぶ手もあります。

本サイトの HTTP Cookie Parser では、SameSite 属性の値ごとに警告メッセージが出るので、自社サービスの Set-Cookie 文字列を貼り付けてチェックしてみてください。

Tool companion

この記事と一緒に使えるツール

Related reading

関連記事