CSRFとSameSite Cookieの攻防戦 —— 悪意あるリンクを踏んだだけで、なぜ自分のアカウントが勝手に操作されるのか?
はじめに:猫の画像を開いただけなのに、なぜ退会処理されたのか?
掲示板やSNSに貼られていた「可愛い子猫の画像まとめ」というリンクをクリックしたとします。 画面には確かに可愛らしい子猫の写真が表示されました。しかしその裏で、あなたがログイン中だったショッピングサイトから勝手に高額商品が注文され、あるいはSNSに「このスパムをクリック!」という恥ずかしい投稿があなたの名前で送信されていました。
パスワードを他人に教えた覚えもなければ、不審なソフトウェアをインストールしたわけでもありません。 それなのに、サーバーはあなたの正規の操作としてそれを受理し、処理を完璧に完了させてしまったのです。
これが、Webの世界で長年猛威を振るってきた**CSRF(Cross-Site Request Forgery:クロスサイト・リクエスト・フォージェリ/関連偽装)**です。
なぜ、全く無関係な外部サイトを開いただけで、別サイトのあなたのアカウントが操られてしまうのでしょうか? その真因は、Webブラウザがユーザーの利便性のために初期から搭載していた**「Cookie(クッキー)の自動送信」という親切すぎる物理挙動**にあります。
1. 悲劇の発端 —— ステートレスなHTTPと「親切すぎるブラウザ」
そもそも、Webの基盤であるHTTPプロトコルは**ステートレス(状態を持たない)**です。 リクエストを1回送ってレスポンスを受け取ったら、通信はそこで完結します。次に送るリクエストが「先ほどログインしたあの人」からのものなのか、世界中の見知らぬ誰かからのものなのか、サーバー単体では見分けがつきません。
この問題を解決するために発明されたのが Cookie(クッキー) です。
ここでブラウザに課された物理的な鉄則は、極めてシンプルなものでした。
「宛先ドメインが一致するなら、それがどんな経由で発生した通信であれ、ブラウザ内に保存されているそのドメインのCookieをすべて自動的にリクエストヘッダに添付して送信する」
この仕組みのおかげで、私たちはページを移動するたびにパスワードを再入力することなく、快適にWebサーフィンを楽しめるようになりました。 しかし、この**「どこから呼ばれても律儀に鍵を届ける」**というブラウザの忠誠心こそが、攻撃者にとって格好の抜け穴となったのです。
2. CSRFの攻撃物理 —— 盲目的な代理人(Confused Deputy)
CSRFの本質は、攻撃者が被害者のパスワードやセッションIDを「盗み出す」ことではありません。 **「ブラウザという正規の代理人を操り、被害者自身のIPアドレスと正規のセッションIDを使って、攻撃者が望む命令を代わりに叩かせる」**点にあります。情報科学ではこれを「困惑した代理人問題(Confused Deputy Problem)」と呼びます。
罠サイトの内部構造
攻撃者が用意した罠サイト(evil-cat-pictures.com)のHTMLソースには、目に見えない形で以下のようなコードが仕込まれています。
<!-- 被害者の目には可愛い猫の画像が見えている -->
<h1>癒やしの猫ちゃんギャラリー</h1>
<img src="cute-cat.jpg" alt="cat" />
<!-- 画面外に隠された不正な送金フォーム -->
<form id="attack-form" action="[https://bank.com/transfer](https://bank.com/transfer)" method="POST" style="display:none;">
<input type="hidden" name="to_account" value="hacker_999" />
<input type="hidden" name="amount" value="100000" />
</form>
<script>
// ページが読み込まれた瞬間に、自動でフォームを送信する!
document.getElementById('attack-form').submit();
</script>
被害者がこのページを開いた瞬間、背後で何が起きるでしょうか。
銀行サーバーの視点に立ってみてください。
送られてきたHTTPリクエストには、有効期限内の正しい session_id=XYZ999 がきっちり添えられています。TCPコネクションの送信元IPも被害者本人のものです。
銀行サーバーには、「被害者が自発的に送金ボタンを押したのか」それとも「裏で開いた別サイトに押させられたのか」を区別する術が物理的に存在しないのです。
3. 旧世代の防壁とその限界 —— 同一起源ポリシー(SOP)の死角
「ブラウザには SOP(Same-Origin Policy:同一起源ポリシー) という強力なセキュリティ壁があるはずでは?」と疑問に思うかもしれません。
ここが多くのエンジニアを混乱させる最大の落とし穴です。 同一起源ポリシーが禁止しているのは、主に**「異なるオリジン(ドメイン等)のレスポンスデータを勝手に『読み取ること(Read)』」**です。
evil.comのJavaScriptが、bank.comから預金残高の数値を読み取ろうとする ➔ SOPが物理的にブロックするevil.comのHTMLが、bank.comへ向けて送金リクエストを書き込んで送信する(Write) ➔ SOPは通過させてしまう!
Webは異なるサイト間でのリンクや画像表示、フォーム送信を前提に発展してきたため、「書き込みリクエストを発行すること」自体を全面禁止することはできなかったのです。
対症療法:CSRFトークン(ワンタイムトークン)の発明
この死角を塞ぐために長年使われてきた標準防壁が、CSRFトークン(Synchronizer Token Pattern) です。
なぜこれが防御になるのでしょうか。
罠サイト evil.com も、被害者のブラウザにPOSTリクエストを送らせることはできます。しかし、SOP(同一起源ポリシー)の壁があるため、evil.com は被害者の画面に埋め込まれた csrf_token の文字列を「盗み見る」ことができません。
トークンを知らない罠サイトが送れるリクエストには正しいトークンが含まれないため、サーバー側で「不正なリクエスト」として弾くことができます。
しかし、この方式には大きな弱点がありました。
- 全てのPOSTフォームやAJAX通信にトークンを漏れなく配備しなければならず、実装漏れ(ヒューマンエラー)が絶えない。
- 画面を持たないモバイルアプリや、REST API、SPA(Single Page Application)との親和性が悪く、設計が極めて煩雑になる。
4. 革命:SameSite Cookie属性による「物理的国境」の創設
「そもそも、なぜ無関係な他人のサイトから飛ばされたリクエストにまで、律儀に我が社のCookieをくっつけて送る必要があるのか?」
この根本的な問いへの回答として、2016年に提唱され、現在のWeb標準となったのが Cookieの SameSite 属性 です。
サーバーはCookieを発行する際、ブラウザに対して「このCookieをクロスサイト(他人の敷地)からの通信に乗せてよいかどうか」を物理的に命令できるようになりました。
Set-Cookie: session_id=XYZ999; Secure; HttpOnly; SameSite=Lax
SameSite の3つの挙動モード
| 属性値 | 他サイトからのリンククリック (GET) | 他サイトからの画像・iframe埋め込み | 他サイトからのPOST送信 (CSRFの主経路) | 特徴と用途 |
|---|---|---|---|---|
None | 送信する | 送信する | 送信する | 従来の無法地帯。広告トラッキング等でのみ利用(Secure必須)。 |
Lax(現代のデフォルト) | 送信する (トップレベル遷移のみ) | ブロック | ブロック | 利便性と安全性の黄金比。 外部からのリンクで入ってきた時はログインを維持しつつ、背後のPOSTや埋め込み攻撃は完全に遮断する。 |
Strict | ブロック | ブロック | ブロック | 完全鎖国モード。 外部サイトのリンクから飛んできた場合、GETであってもCookieを送らない(一度トップ画面を経由する必要がある)。 |
Lax がもたらした劇的勝利
現在、Google ChromeやSafari、Edgeなどの主要ブラウザは、SameSite 属性が明示されていないCookieを**自動的に SameSite=Lax として扱う仕様(Lax by default)**を標準化しました。
これにより、先ほどの罠サイト evil.cat-pictures.com から発行された自動POSTリクエストは、被害者のブラウザによって**「これはクロスサイトからのPOSTだな」と判定され、Cookieがその場で強制的に剥ぎ取られます。**
銀行サーバーに届くリクエストにはCookieが一切乗っていないため、サーバーは「ログインしていない名無しからのアクセス」として即座に破棄します。 数行の属性定義によって、CSRF攻撃の主要経路は物理的に塞がれたのです。
触って試す:CSRF攻撃のステップ実行と比較
🎣 CSRF攻撃のステップ実行と比較
まず銀行のCookie設定を選び、「次へ」で攻撃の流れを1ステップずつ追ってください。設定を変えて同じ攻撃をもう一度流すと、どこで止まるか(止まらないか)を比べられます。
5. 現代に残る盲点 —— なぜ「SameSite=Lax」でも油断できないのか?
「ブラウザがデフォルトでLaxにしてくれるなら、開発者はもうCSRFを気にしなくてよいのか?」 残念ながら、エンジニアがアーキテクチャの原則を破ると、依然として穴が空きます。
盲点①:GETリクエストで状態を変更する最悪の設計
SameSite=Lax は、「ユーザーがリンクを踏んで別サイトへジャンプする通常のGET遷移」ではCookieの送信を許可します。
もし開発者が、「手軽だから」という理由で以下のようなGETで実行できる退会・送金APIを作っていたらどうなるでしょうか。
[https://bank.com/account/delete?confirm=yes](https://bank.com/account/delete?confirm=yes)
攻撃者はフォームを作る必要すらありません。罠サイトに <a href="https://bank.com/account/delete?confirm=yes">ここをクリック</a> と貼っておくだけで、ユーザーが踏んだ瞬間にCookieが送信され、一撃でアカウントが吹き飛びます。
**「GETは参照のみ、副作用(データの書き換え・破棄)を伴う処理は必ずPOST/PUT/DELETEで行う」**というHTTPの原則は、セキュリティの絶対防壁でもあるのです。
盲点②:XSS(クロスサイトスクリプティング)との連鎖
CSRFが「外部サイトから指示を出す」攻撃であるのに対し、XSSは「標的サイト自身の内部に悪意あるJavaScriptを直接注入する」攻撃です。
もし同一ドメイン内にXSS脆弱性が1箇所でもあれば、攻撃者のスクリプトは bank.com の敷地内(Same-Site)から直接リクエストを発行できます。
この場合、SameSite=Strict にしていようが、CSRFトークンを導入していようが、すべて敷地内から正規に取得・送信されてしまうため、防壁は完全に崩壊します。
📝 3行で頭に入る『試験・実務の要点まとめ』
- 攻撃の物理:ブラウザは宛先ドメインが合致すれば送信元を問わずCookieを自動添付するため、罠サイトから発行された不正リクエストも「本人の操作」として誤認される。
- SameSite革命:Cookieに
SameSite=Lax(現代のブラウザ標準)を付与することで、外部サイトからのPOST通信や埋め込み通信からCookieが物理的に剥奪され、CSRFが無力化される。 - 実務の絶対鉄則:副作用のある状態変更処理をGETメソッドに割り当てない設計を徹底し、二重防御としてフレームワーク標準のCSRFトークン検証を併用する。