19
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CSRF攻撃をちゃんと理解したい人のための入門:CSRFトークンは銀の弾丸ではない!

Last updated at Posted at 2025-06-23

🔐 他のセキュリティ系記事もよかったらどうぞ:

CSRFの仕組みと防御を読み解く:CSRFトークンは銀の弾丸ではない

✅ この記事で触れること

  • CSRF(クロスサイト・リクエスト・フォージェリ)の攻撃原理と実例
  • ブラウザ仕様としてのCookie自動送信の仕組み
  • CSRF トークン方式の仕組みと実装例(Laravel)
  • SameSite 属性(Strict / Lax / None)の違いと選択基準
  • SPA + Cognito など最近の構成での CSRF 対策の実情
  • JWT の保存場所による CSRF 耐性・XSS 耐性の違い
  • UX とセキュリティのバランスがなぜ難しいか

❌ この記事で触れないこと

  • OAuth 2.0 や OIDC そのものの認可フロー詳細
  • 脆弱性スキャナや WAF による CSRF 検出ルールの運用
  • JWT の署名検証・暗号方式・失効処理といった低レイヤー実装
  • SSRF / Clickjacking / Session Fixation 等の他の認証系攻撃
  • WebAuthn や Passkey など次世代認証技術

CSRFとは

認証済みのユーザーが、攻撃者の用意したリクエストを意図せず Cookie 付きで送信させられてしまう攻撃。

CSRF の影響を受けるのは、認証や認可に基づいて動作し、主に POST 等のリクエストを受け付けるサイト。

☠ 攻撃のシナリオ ☠

ユーザーの行動例:

  1. インターネットバンキングにログインし、口座の残高を確認していた(特にログアウト操作はせず、そのままにしておいた)
  2. Facebook をうろうろしていたら、可愛い猫写真のサイトが気になってクリック!
  3. ☠ 猫 写真館(cat-evil.com)は罠サイト ☠
  4. 仕込まれた JS により、ブラウザに保存されていたインターネットバンキングのセッションクッキーを添えて不正リクエストが送信される

でも "銀行" と "罠サイト" はドメイン違うから安全では?

F12 で見える Cookie は「同一ドメインのものだけ」だったはず 🤔

これはブラウザの Same-Origin Policy(同一生成元ポリシー) による厳重な制限です。
でも… 「送信」はされてしまうのがポイント!

<form action="https://bank.example.com/send" method="POST">
  <input type="hidden" name="amount" value="100000">
</form>
<script>document.forms[0].submit();</script>

このようなHTMLで、bank.example.com の Cookie が自動で付与されてしまう。

なぜそんな仕様? ブラウザの不具合では

ブラウザが勝手に Cookie を送るのは 「便利さ」と「安全性のバランス」のため。

「送信先のドメインに対応するクッキーがあれば必ず送る」のが原則です。

今開いているページのドメインとは関係ありません!

もし全ての Cookie 付きリクエストをブロックしてしまうと…

  • シングルサインオン(SSO)
  • ログイン中のページ遷移
  • 複数タブでのログイン維持

など、一般的な Web の利便性が失われてしまうため。

じゃあどうやって防ぐ?

IPA は「意図されたリクエストかどうかを確認する仕組みの導入」を推奨しています。

実装例として広く使われているのが、CSRFトークン方式です。

これは正規のフォームにしか埋め込めないランダム値を使って、「本物のリクエストであること」をサーバー側で検証する方法です。

具体的には…
正規サイトのフォーム内に CSRFトークン を配置し、リクエスト送信時に一緒に送信させます。

  • サーバー側でセッションストアのトークンと送信されたトークンを比較
  • 一致した場合のみリクエスト受付
  • 不一致の場合は 403 Forbidden

多くのフレームワークでの実装例(Laravelの場合)

多くのWebフレームワーク(Django, Rails, Laravelなど)では、CSRFトークンによる保護が標準で備わっており、POSTなどの状態変更リクエストにはトークンが必須になります。

ここではその一例として、Laravel の実装方法を紹介します。
対策:

@csrf

これで以下の input が自動生成:

<input type="hidden" name="_token" value="ランダムな文字列">

検証:

ミドルウェア VerifyCsrfToken が POST リクエスト時に、セッション内トークンと送信トークンを比較

✅ ただし... CSRFトークンは銀の弾丸ではない!

CSRF 対策の目的は「本人しか送れない仕組み」を作ること。

でも、「本人が送ってるつもりなのに失敗する」ような UX になってしまうと、それはそれで破綻しています。

😵‍💫 CSRFトークンでありがちな「UX 崩壊」パターン

  • 長文入力して送信 → トークン期限切れで 403
  • 複数タブで同じ画面を開いて操作 → 片方がトークン競合で失敗
  • 自動保存つきSPA → トークン更新されず保存失敗(しかも静かに)
  • ページ遷移せず fetch() だけ → 古いトークンのまま送信

→ ユーザー「なんでエラーになったの?」と困惑するやつです。
だからこそ、「ちょうどいいセキュリティ設計」 を探すのがエンジニアの仕事 ✨

SameSite Cookie の補助的効果

クッキーを発行する際に「別サイトからのアクセスには絶対このクッキー送らない」といった指示を設定できます

res.cookie('session', 'abc123', {
  httpOnly: true,
  sameSite: 'Lax',
});

属性の違い

属性 説明 主な用途
Strict 全クロスドメインでCookie送信拒否 金融機関など安全性最優先サイト
Lax クロスドメインのGETは送信OK 通常のSPA構成
None すべて許可(HTTPS必須) サブドメイン/連携用途

SameSite Cookie の限界と問題点

  • 古いブラウザで未対応
  • クロスサイト動作(サードパーティ iframe・認証プロバイダ等)では SameSite=None 必須

CSRF対策の多くは「ブラウザが正しく動作すること」を前提としています。
特に SameSite 属性などは、古いバージョンのブラウザではそもそも無視されることもあるため、ユーザー環境に依存した脆弱性も生まれがちです。

セキュリティ対応の観点からも、ブラウザは常に最新に保つ のがおすすめです。

AWS Cognito Hosted UI構成 + Next.js とかの SPA だと?

SameSite=Lax が現実的

  • 同一ドメインでログイン後画面遷移も同一オリジン
  • PKCEフローでトークン取得 → API通信

Lax がバランス良 ◎

SameSite=Strict だと?

  • ブックマークやメールリンク再訪時にクッキーが送られず、ログイン状態が維持されない

SameSite=None を使うべきケースは?

  • サブドメインでAPIとフロントを分けている場合
  • 認証プロバイダとの連携などクロスドメイン必須時

SameSite=None; Secure が必要

※ SameSite=None にすると、Secure(HTTPS)属性が必須!(ブラウザ仕様で強制)

JWT なら CSRF は安心?

  • 一般的に JWT の保存場所は localStoragesessionStorage が使用される
  • リクエストに、明示的に Authorization: Bearer を付ける必要あり
    → 自動送信されないので CSRF 耐性は強い

ただし:XSS に弱い(JS で読み取れる)

JWT でも HttpOnly Cookie に保存すれば CSRF の影響を受けます
トークンの送信方法(ヘッダかCookieか)で耐性が決まります。

総まとめ

  • CSRF は「Cookie を盗む」のではなく「使わせる」攻撃

  • 対策は:

    • Cookie 制御(SameSite)
      → ブラウザに任せて抑制できるが、制限が強すぎると利便性が損なわれる
      → 古いブラウザでは意図通りに機能しないことも
    • 操作の検証(CSRFトークン)
      → 本人の操作かどうかをサーバー側で判定できるが、実装ミスやトークンの有効期限管理でUXを損ないやすい
    • 認証設計全体の見直し
      → JWT や SPA 構成を含めた、アプリケーション全体の認証・認可の仕組みと保存場所の整理が重要!

🐍 小さな蛇足

JWT 認証なのに Cookie が使われる? → Cognito はログイン UX 改善のため、セッションクッキーを併用しています。

OAuth2/OIDC は「どう保存するか」までは決めていません → 保存方式は実装に委ねられている。


ここまで読んでいただき、ありがとうございました!

19
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?