Cookie と CSRF のアンチパターン集
BFF / SPA / IdP 実装で繰り返し踏まれるパターンをまとめたもの。 各項目は「症状 → なぜダメか → 正しくは」の形式。
前提:なぜ CSRF が起きるのか
Cookie は ambient authority(環境権限)であり、保持者が明示的に提示しなくても自動で効く。 これは仕様上の性質であってバグではない。CSRF 対策とは、この性質を後付けで打ち消す作業である。
元々の設計:発信元を一切見なかった
Cookie の送信可否は、本来「送信先が Domain / Path 条件に合致するか」だけで決まっていた。 リクエストがどのページから発生したかは判定に入らない。
発信元: https://evil.com ← 判定に使われない(SameSite 以前)
送信先: https://bff.example.com/api/transfer
→ Domain / Path が合致するので Cookie を添付
サーバ側から見ると、正規の画面から来たリクエストと完全に見分けがつかない。 Cookie は正しく、セッションも有効だからである。
現在:発信元も見るようになった(ただし別の単位で)
SameSite の既定化(2020年前後)以降、ブラウザは発信元も考慮する。
- SameSite 判定のために発信元の site を見る
Originヘッダを付与するSec-Fetch-Siteを付与する
判定単位が3つに分かれているのが混乱の元
同じ「クロスサイトかどうか」に見えて、仕組みごとに判定単位が異なる。
| 仕組み | 判定単位 | a.example.com と b.example.com |
|---|---|---|
SOP / CORS / Origin ヘッダ | オリジン(scheme://host:port) | 別物 |
| SameSite | site(schemeful eTLD+1) | 同じ |
| Cookie のスコープ | Domain 属性 + Path(ポートは無視) | Domain 指定次第 |
Cookie の送信先マッチングはオリジン単位ではなく、Cookie 独自の Domain / Path ルールで決まる。 ポートを区別しない点に注意(F-1 参照)。
B-1 のサブドメイン穴はこの単位のズレが原因である。
CORS はオリジンで判定するので evil.example.com を別物として扱えるが、
SameSite は site で判定するため同一サイト扱いになり素通しする。