SameSite属性
セームサイトぞくせい
クロスサイトのリクエストにCookieを載せるかを制御する属性。CSRF対策の第一線。
概要
SameSite は、Cookie に付ける属性のひとつで、「別のサイトから発生したリクエストに、この Cookie を載せて送るか」をブラウザに指示するものです。値は Strict・Lax・None の3つで、Strict に近づくほどクロスサイト(別サイト発)のリクエストで Cookie が送られなくなります。
Cookie の最大の特徴は「条件を満たすリクエストにブラウザが自動で添付する」ことですが、この自動送信は、リクエストがどのサイトのページから発生したかを問いませんでした。罠サイトに置かれたフォームやタグが標的サイトへリクエストを飛ばしても、ログイン中のセッション Cookie が付いて「本人の操作」として通ってしまう — これが CSRF の前提です。SameSite はこの前提そのものをブラウザのレベルで崩す、防御側にとっての第一防衛線です。
なぜ生まれたか
CSRF への古典的な対策は、アプリ側でトークンを発行・検証することでした。しかしこれは全アプリが自前で正しく実装しなければならない対策であり、実装漏れや検証ミスが後を絶ちませんでした。一方、問題の根源は「ブラウザがどこ由来のリクエストにも無差別に Cookie を添える」という仕様側の挙動です。それなら仕様側で直そう、というのが SameSite の発想で、2016年前後に Chrome と Firefox に実装され、Cookie 仕様の改訂案(RFC 6265bis)に取り込まれました。
転機は 2020 年、Chrome が「SameSite 未指定の Cookie を Lax として扱う」というデフォルト変更に踏み切ったことです。それまで未指定の Cookie はすべてクロスサイトに送られていた(現在の None 相当)のが、宣言なしではクロスサイト送信されないのが既定になりました。Web 全体の CSRF 耐性を一夜にして底上げした大きな変更ですが、同時に、クロスサイト連携に Cookie を使っていた正規のサービス(決済のリダイレクトや SSO など)が軒並み影響を受け、「ブラウザの既定値の変更が世界中のサイトを壊しうる」ことを示した事件としても記憶されています。
詳細
3つの値の挙動を正確に押さえる
まず前提として、ここでいう「サイト」はドメインの登録可能な単位(例: example.com)を指し、オリジンより粗い括りです。app.example.com と shop.example.com の間のリクエストは「同一サイト」扱いになります。その上で、リクエストの種類ごとに3つの値がどう振る舞うかを一覧で押さえます。
Strict はクロスサイト発のリクエストには一切 Cookie を送りません。もっとも堅い設定ですが、他サイトのリンクからログイン中のサイトへ移動したときもログアウト状態に見えるため、体験を損ないやすい値です。Lax はそこに一箇所だけ例外を設けます。「トップレベルの遷移(アドレスバーの URL が変わる移動)かつ安全なメソッド(GET)」の場合だけは Cookie を送る、というものです。リンクをたどって来た訪問者はログイン状態のまま迎えつつ、状態を変更する POST や、ページ内に隠れた img・iframe・fetch には送らない — 利便性と防御の落とし所として設計されたのが Lax です。None は従来どおり無条件に送る指定で、クロスサイトの埋め込みで Cookie が必要な正規のケース(別サイトに埋め込むウィジェットや SSO など)のための値です。ただし None を指定するには Secure 属性(HTTPS でのみ送信)が必須で、平文通信でクロスサイト送信される Cookie はブラウザが受け付けません。
表を眺めるだけでなく、属性・発生元・リクエストの種類を自分で組み合わせて「Cookie が送られるか」を予想しながら確かめてみてください。
| Strict | Lax | None | |
|---|---|---|---|
| 同一サイト | · | · | · |
| クロス: トップレベル GET | · | · | · |
| クロス: フォーム POST | · | · | · |
| クロス: 埋め込み / fetch | · | · | · |
Cookie の属性・発生元・リクエストの種類を選んで「送信してみる」を押してください。まず予想してから試すのがおすすめです。
CSRF 対策としての位置づけ — 単独では守りきれない
Lax が既定になった今、「CSRF はもう過去の攻撃では」と思いたくなりますが、SameSite 単独に頼るのは危険です。理由はいくつかあります。第一に、Lax はトップレベルの GET 遷移では Cookie を送るため、状態変更を GET で実装してしまっているエンドポイントは依然として無防備です。第二に、「サイト」の括りはサブドメインを区別しないため、blog.example.com のような同一サイト内のどこかに XSS や自由に HTML を置ける場所があると、そこを踏み台にしたリクエストは「同一サイト」として Cookie 付きで通ります。第三に、古いブラウザや一部の互換挙動(Chrome には None 相当として扱う導入後2分間の猶予措置がありました)など、境界条件が残ります。したがって防御側の結論は明快で、重要な操作には従来どおり CSRF トークンによる検証を実装し、SameSite はそれを底上げする層として重ねる、という多層防御が現在の標準です。
実務の落とし穴 — 正規のクロスサイト連携が壊れる
SameSite でつまずくのは、攻撃よりもむしろ正規の連携です。典型が OAuth や SSO のリダイレクトで、認可サーバから redirect_uri へ POST で戻ってくるフロー(OIDC の form_post など)では、戻り先のリクエストがクロスサイトの POST になるため、Lax や Strict のセッション Cookie が届かず「ログインしたはずなのに未ログイン扱い」という不具合になります。決済代行からの戻り、埋め込み型のウィジェット、iframe 内で動くアプリも同様です。こうしたケースでは、該当の Cookie だけを SameSite=None; Secure にする、状態の受け渡しを Cookie 以外(リクエストパラメータの state など)に持たせる、といった設計判断が必要になります。なお、SameSite はあくまで「Cookie を送るか」の制御であり、リクエスト自体は届く点に注意してください。レスポンスの読み取りを制御する CORS とは守っている場所が違い、CSRF・情報漏えい・スクリプト実行はそれぞれ別の防御(SameSite・CORS・CSP)が受け持つ、と整理して覚えるのが確実です。