はじめに
SameSite Cookie は、CSRF 対策として広く採用されている
ブラウザ主導の防御メカニズムです。
多くの開発者はこう考えます。
「SameSite を設定しているから CSRF は大丈夫」
しかし実際のペンテスト現場では、
SameSite を正しく理解していない実装が、
そのまま 攻撃ベクトルになることが珍しくありません。
本記事では、
- SameSite の基本設計
- SameSite=Lax を悪用した CSRF(GET)
- Lax + POST を成立させる Chrome の仕様例外
- ペンテスター視点での本質的な問題点
を、実攻撃シナリオに沿って解説します。
SameSite Cookie とは何か(要点整理)
SameSite 属性は、Cookie に付与される制御フラグで、
「この Cookie、クロスサイトリクエストでも送る?」
という判断を ブラウザに任せるための仕組みです。
SameSite の 3 つのモード
SameSite=Strict
- Cookie は 完全に同一サイトのみ
- クロスサイト経由では 一切送信されない
✔ CSRF 耐性:最強
✖ UX:外部リンクでログイン切れが発生しやすい
SameSite=Lax
- 原則クロスサイトでは送信されない
- ただし以下は 例外的に送信される
- トップレベルナビゲーション
- GET / HEAD / OPTIONS
✔ UX とセキュリティの妥協点
⚠️ 「部分的に送られる」ことが最大の罠
SameSite=None
- すべてのクロスサイトリクエストで送信
- Secure(HTTPS)必須
✔ iframe / SSO 用
✖ CSRF 防御としては期待不可
SameSite=Lax を使った CSRF 攻撃(GET)
攻撃シナリオ概要
- 被害者 Josh は銀行サイト
mybank.thmにログイン中 - ログイン時に logout Cookie(SameSite=Lax) が発行される
- サーバーは Cookie の値のみでログアウト処理を実行
if ($_COOKIE["logout"] == "xxxxxxx") {
session_destroy();
}
攻撃者の目的
Josh を強制ログアウトさせる
攻撃方法
攻撃者は Josh に以下のようなメールを送信します。
<a href="https://mybank.thm:8080/logout.php" target="_blank">
Survey Link!
</a>
なぜ成立するのか?
-
<a>クリック = トップレベルナビゲーション - HTTP メソッド = GET
- SameSite=Lax → Cookie が送信される
結果:
- logout Cookie が送信される
- サーバーは正規操作と誤認
- Josh は即ログアウト
何が問題だったのか?
設計上のミス
- 状態変更(logout)を GET で実装
- SameSite=Lax を「安全」と誤解
正しい設計なら?
- logout は POST
- CSRF トークン必須
- 重要 Cookie は SameSite=Strict
「Lax なら POST は安全」は本当か?
答えは NO です。
Chrome の SameSite 例外仕様(2 分ルール)
Google Chrome の公式仕様より要約すると:
SameSite 属性が 指定されていない Cookie は、
設定・更新から 2 分以内であれば、
POST を含むクロスサイトリクエストでも送信される。
つまり:
- Cookie 更新直後
- 一時的に SameSite=None のように振る舞う
Lax + POST を成立させる攻撃チェーン
攻撃対象
-
isBannedCookie - 値によってユーザー状態を制御
サーバー側コード(抜粋)
if (!isset($_COOKIE['isBanned'])) exit();
if (isset($_POST['isBanned'])) {
document.cookie="isBanned=".$_POST['isBanned'];
}
開発者の意図
- Cookie が無い POST を拒否
- CSRF 対策のつもり
単純な POST が失敗する理由
- クロスサイト POST
- SameSite=Lax
- Cookie が送信されない
👉 攻撃失敗 ❌
攻撃者の発想転換(重要)
-
isBannedCookie は logout 時に更新 - 更新後 2 分間は POST でも送信される
完成した攻撃チェーン
- 被害者を logout に誘導
- Cookie が更新される
- 2 分以内に POST を実行
- Cookie が送信される
-
isBanned=trueに変更成功
実際の攻撃コード例
<script>
function launchAttackSuccess(){
let win = window.open("http://mybank.thm:8080/logout.php",'');
setTimeout(function(){
win.close();
bank.submit();
},1000);
}
</script>
<form name="bank" method="post"
action="http://mybank.thm:8080/index.php" style="display:none">
<input name="isBanned" value="true">
</form>
ペンテスター視点の教訓
- SameSite=Lax は 防御ではなく制限
- Cookie 更新タイミングは攻撃面
- Cookie を 認可・状態管理に使う設計自体が危険
防御側のベストプラクティス
- 状態変更は POST + CSRF トークン
- セッション Cookie は
SameSite=Strict - Cookie をロジック判断に使わない
- クライアント JS で Cookie を書き換えない
まとめ
SameSite は優秀ですが、万能ではありません。
SameSite は「盾」ではなく「条件付きルール」
特に Lax は最も誤解されやすい設定です。
CSRF を本気で防ぐなら、
SameSite + CSRF Token + 正しい設計
これが揃って、初めて「安全」と言えます。