WordPressで考えるSameSite=Laxの限界|Cookie更新でCSRFが成立する理由

CSRF対策として「SameSiteを設定しているから安全」と考えていないでしょうか。

SameSite=Laxは、クロスサイトのPOSTリクエストに対してCookieを送らないことで、CSRFを防ぐ仕組みです。

しかし、条件によってはSameSite=LaxでもCSRFが成立するケースがあります。

この記事では、Web Security Academyのラボ「SameSite Lax bypass via cookie refresh」をもとに、

  • なぜSameSite=Laxでも防げないのか
  • Cookieの発行タイミングが攻撃にどう影響するのか
  • WordPressで起こり得る実装上の問題

を整理します。

CSRFの基本的な仕組みについては、以下の記事で整理しています。

CSRFとは何か|成立条件と対策の基本

SameSite=Laxとは何か|Cookie送信の基本ルール

SameSiteは、Cookieをどのリクエストで送信するかを制御する属性です。

SameSite=Laxの場合、基本的な挙動は次の通りです。

  • 同一サイトのリクエストでは送信される
  • クロスサイトのGET(リンク遷移など)では送信される
  • クロスサイトのPOSTでは送信されない

この仕様により、外部サイトからのフォーム送信によるCSRFを防ぐことができます。


SameSite=LaxでもCSRFが成立する理由

SameSite=LaxにおけるCookieの発行タイミングとCSRF成立条件の違いを示した図解

ブラウザには次の仕様があります。

  • SameSite属性が未指定のCookieはLaxとして扱われる
  • 新しく発行されたCookieは、一定時間だけ制限が緩くなる

この一定時間はおよそ2分です。

つまり、次の条件が揃うと、本来送られないはずのCookieが送信されます。

  • セッションCookieが新しく発行された直後
  • クロスサイトからのPOSTリクエスト

この例外によって、SameSite=LaxでもCSRFが成立します。

CSRFはSameSiteだけで防げるものではなく、トークン設計にも依存します。

WordPressで考えるCSRFトークンの欠陥│セッションと紐づいてないトークンはなぜ危険なのか


攻撃の流れ|Cookie更新を利用したCSRFの仕組み

ラボで行われている攻撃はシンプルです。

  1. OAuthログインなどを利用して新しいセッションCookieを発行させる
  2. その直後に状態変更リクエストを送信する
  3. SameSite制限が緩い状態を利用してCookie付きでリクエストを通す

重要なのは、Cookieが新しく発行された直後というタイミングです。

同じCSRFでも、実装の違いによって成立条件は大きく変わります。

WordPressで考えるSameSite Laxの落とし穴|GETに見せかけてPOSTとして処理されるCSRF


攻撃者視点で見るSameSite回避のポイント

攻撃者の視点では、この脆弱性は次のように見えます。

まず注目するのは、SameSiteの設定そのものではなく、Cookieが再発行されるタイミングです。

通常、SameSite=Laxが設定されている場合、クロスサイトのPOSTリクエストではCookieは送信されません。

しかし、ログイン直後などでCookieが新しく発行された場合、この制限が一時的に緩和されることがあります。

そのため攻撃者は、単純にCSRFを試すのではなく、

  • ログイン処理やOAuthフローを利用してセッションを更新させる
  • その直後に状態変更リクエストを送信する

という流れを組み立てます。

重要なのは、被害者がすでにログインしているかどうかではなく、直前にログイン処理を発生させられるかどうかです。

このように、SameSiteの回避は設定ミスではなく、仕様とタイミングを組み合わせた攻撃として成立します。


Cookieの発行タイミングが重要な理由

SameSite=Laxには、新しく発行されたCookieは約2分間だけ制限が緩くなるという仕様があります。

しかし実際の検証では、2分以内でも失敗することがあります。

Cookie発行タイミングのズレ

カウントはログイン操作ではなく、Cookieが実際に発行された時点から始まります。
OAuthではリダイレクト途中で発行されるため、体感より早く時間が進んでいることがあります。

処理遅延とユーザー操作

リダイレクトやページ読み込み、クリック操作の間に数秒〜数十秒消費されます。
その結果、気づかないうちに条件を外れることがあります。

Cookieが更新されていない

SameSiteの例外が適用されるのは、新しく発行されたCookieのみです。
ログイン状態が再利用されている場合は、条件を満たしません。

ポップアップブロック

window.open()はユーザー操作と紐づいていないとブロックされます。
これにより、Cookie更新自体が行われないケースがあります。


CSRFが失敗する理由まとめ

2分以内はあくまで上限であり、実際には

  • Cookieの発行タイミング
  • 処理遅延
  • ユーザー操作
  • Cookieの更新状態

に大きく左右されます。

SameSiteは時間だけでなく、状態とタイミングに依存する仕組みです。


なぜ防げないのか

SameSiteはリクエストの種類に基づく制御ですが、実際にはCookieの状態にも依存しています。

  • 古いCookie → 制限が適用される
  • 新しいCookie → 一時的に制限が緩くなる

このため、SameSite=LaxだけではCSRF対策として不十分になる場合があります。


検証中にハマったポイント

今回のラボで最も分かりにくかったのは、Cookieがいつ発行されたかでした。

最初は単純にログイン状態を見落としているのではないかと考えていましたが、実際の原因はそこではありませんでした。

Cookieが新鮮な状態でないと成立しない

このラボでは、OAuthログインによって新しいセッションCookieが発行された直後に攻撃を行う必要があります。

しかし実際の検証では、

  • すでにログイン済みの状態で検証を続けていた
  • その状態で正しいペイロードをDeliverしていた

という状況になっていました。

この場合、見た目はログイン済みですが、セッションCookieはすでに発行から時間が経過しており、新しく発行されたCookieではありません。

その結果、

  • SameSiteの制限がそのまま適用される
  • クロスサイトPOSTでCookieが送信されない
  • CSRFが成立しない

という状態になります。

正しい手順は、ログイン直後に攻撃すること

このラボで重要なのは、次の順序です。

  1. OAuthログインを実行する
  2. 新しいセッションCookieが発行される
  3. その直後にExploitをDeliverする

この順序を満たさないと、SameSiteの例外条件が成立しません。

なぜ気づきにくいのか

この問題が厄介なのは、見た目では区別がつかない点です。

  • どちらもログイン済みの状態に見える
  • UI上は同じように操作できる

しかし内部では、

  • 新しいCookieか
  • 古いCookieか

という違いがあり、ここで挙動が変わります。

Cookie削除で解決した理由

最終的に、ブラウザのCookieを削除してから再度検証を行ったところ、正常に攻撃が成立しました。

これは、

  • セッション状態が完全にリセットされた
  • OAuthログインによって新しいCookieが発行された
  • その直後に攻撃を実行できた

ためです。


WordPressで起こり得るケース

ソーシャルログイン

GoogleログインなどのOAuthを導入している場合、ログイン完了時に新しいセッションCookieが発行されます。

その直後に状態変更リクエストが可能な設計だと、SameSiteの例外条件が成立します。

CSRFトークン未実装

以下のような機能にCSRFトークンがない場合、SameSiteに依存した防御になります。

  • メールアドレス変更
  • プロフィール更新
  • パスワード変更

この状態では、Cookieの更新タイミングを利用した攻撃を防げません。

ログイン直後の処理

ログイン直後に自動で処理を行う設計の場合、外部からの誘導で意図しないリクエストが混ざる可能性があります。


セキュリティ診断でのチェックポイント

以下を確認することで、このタイプの問題を発見できます。

  • SameSiteが明示的に設定されているか
  • 状態変更リクエストにCSRFトークンがあるか
  • OAuthログイン後にCookieが再発行されるか
  • ログイン直後の挙動に変化がないか

特に「ログイン直後のみ成立する挙動」は見落とされやすいポイントです。


SameSiteに頼らないCSRF対策|WordPressでの実装方法

CSRFトークンの導入

WordPressではnonceを使用します。

  • wp_create_nonce
  • check_admin_referer

SameSiteに依存せず、リクエストごとに検証を行う必要があります。

SameSiteの明示設定

  • SameSite属性を明示する
  • 可能であればStrictの利用も検討する

デフォルト任せにしないことが重要です。

ログイン後の設計見直し

  • ログイン直後に重要な操作を行わせない
  • 再確認や再認証を挟む

タイミング依存の挙動を減らすことが重要です。


まとめ|SameSite=Laxは補助的な対策に過ぎない

SameSite=Laxは有効なCSRF対策の一つですが、それだけでは不十分です。

  • Cookieは発行タイミングによって挙動が変わる
  • ログイン直後は制限が緩くなる
  • 状態変更処理は必ずトークンで保護する必要がある

SameSiteは補助的な対策として扱い、他の防御と組み合わせることが前提となります。

SameSite以外のCSRFパターンについても、以下の記事で整理しています。

WordPressで考えるCSRF|メール変更機能を悪用する攻撃
WordPressで考えるCSRF対策の落とし穴|POSTリクエストのみの検証では防げない理由
WordPressで考えるCSRF対策の落とし穴|トークン未送信で突破されるケース