Chrome の SameSite 属性の仕様によりアプリケーション不具合が起きた話

これは エキサイトホールディングス Advent Calendar 2023 の3日目の記事です。

最近は業務でたまに PHP を書いているのですが、Chrome の SameSite 属性の挙動により四苦八苦したのでその内容をまとめてみようと思います。

目次

結論

先に結論です。 前提として、Chrome ブラウザに限った話です。Firefox など他のブラウザでは微妙に仕様が異なります。
Cookie に設定できる SameSite 属性に Lax を設定していると、Cross Site な POST リクエストを利用する場合は Cookie が発行されてから 2分間だけその POST リクエストに Cookie がセットされます。2分を過ぎると POST リクエストには Cookie がセットされなくなるので、アプリケーションの挙動としておかしくなる可能性があります。 SameSite=None を利用するか、POST を GET にするか、Cookie を利用しないようにするか、Cookie の発行タイミングを調整するなどの対応を取る必要があるでしょう。

SameSite 属性と Chrome の仕様

自分が四苦八苦したケースを説明する前に簡単に SameSite 属性と SameSite に対する Chrome の仕様の説明をしておきます。今回の事象を解説するための最低限の説明なので詳しくは参考 URL などをご覧ください。

Same Site の条件

以下の全てが一致すれば Same Site となり、1つでも異なれば Cross Site です。

  • スキーマ(http, https など)
  • eTLD + 1 (例: example.co.jp , example.com など)
    ※ eTLD(effective Top Level Domain) とは、.com.org など Root Zone Database に掲載されてるTLD とそのすぐ左側を合わせたものです。.co.jp.github.io など、実質的にTLDとして扱われてるものもあり、これらは Public Suffix List にまとめられています

例えば、https://sub1.example.comhttps://sub2.example.com は Same Site であり、https://example1.comhttps://example2.com は Same Site ではありません。

SameSite 属性について

Cookie を発行する際に設定できる SameSite 属性は 3 つの値を設定できます。

設定値 説明
None 全ての cross-site なリクエストに対して Cookie が付与されます
Strict same-site に対するリクエストにのみ Cookie が付与されます
Lax GET リクエストでの Cross Site のページ遷移や Same Site の POST, GET リクエストにのみ Cookie が付与されます。一方、POST メソッドのような CSRF の危険性が高い HTTP メソッドによる Cross Site なリクエストに対しては Cookie が付与されません。Cross Site で POST リクエストする場合は Cookie が飛ばないので悪意のあるサイトからの POST による CSRF 攻撃の対策になります。

※ ちなみに、Chrome 84 以降 SameSite 値が設定されていない Cookie は SameSite=Lax として扱われるようになりました

SameSite 属性に関する Chrome の仕様

Chrome では SameSite=Lax かつ Cross Site な POST リクエストをした際の挙動が上記で説明した Lax の挙動と少し異なります。 それは、Cookie が生成されたタイミングから2分以内であれば Cross Site な POST リクエストに Cookie が付与され、2分以降は Cookie を付与しないという点です。

実際に自分が遭遇したケースを見てみましょう。

不具合が起きたケース

PHP で他社システムにリダイレクトをしながら行うある登録処理を開発をしていたときに、Chrome で登録処理が成功したり失敗したりする現象にあいました。
A サイトを自社で管理するサイト、B サイトを他社で管理するサイトとして、その処理の流れを下記に示します。前提としてユーザが A サイトにログイン済みでセッションIDが SameSite=Lax で Cookie にセットさているものとします。

  1. A サイトでユーザが情報を入力し POST リクエストを送信
  2. A サイトで登録処理のトランザクションIDを発行しキーバリューストアにセッションIDをキーとしてトランザクションIDを登録
  3. B サイトへリダイレクト
  4. B サイトでももろもろの処理後に A サイトへ POST リクエストのリダイレクト
  5. キーバリューストアからセッションIDをキーとしてトランザクションIDを取得
  6. トランザクションIDを利用して登録処理実施

不具合が起きた一連の処理

A サイトと B サイトは完全に別ドメインなので Cross Site となります。また、B サイトから A サイトへのリダイレクトは POST リクエストなので今回紹介した仕様にあてはまります。起こる現象としては、セッションIDがセットされた Cookie が発行されるのはログインの時なので、ログインから2分以内に A サイトにリダイレクトで戻ってこれるとその A サイトへの POST リクエストに Cookie が付与され、キーバリューストアからトランザクションIDを取得することができ登録が成功します。しかし、ログインから2分以降に A サイトにリダイレクトで戻ってくるとその POST リクエストには Cookie がセットされていないので、サーバ側にセッションIDが伝わらずキーバリューストアからのトランザクションIDの取得に失敗します。

対応方法

対応としては以下の方法があるかと思います。

  1. Cookie を利用しなくてもいいか検討する
  2. POST を GET にできないか検討する
  3. SameSite=None を利用する
  4. Cookie の発行タイミングを調整する

今回はアプリケーション側で CSRF 対策をした上で 3 の SameSite=None で対応しました。

また、今回紹介した Lax + POST の組み合わせによる2分縛りはいずれなくなり、2分経過することなく Cookie がセットされなくなるようです。時間をリセットするためにセッションIDを再生成して Cookie に設定し直す方法もあるかとは思いますが、いつ仕様が変わるか分からないのでこの方法は取りませんでした。 https://www.chromium.org/updates/same-site/

Note that the 2-minute window for "Lax+POST" is a temporary intervention and will be removed at some point in the future (some time after the Stable launch of Chrome 80), at which point cookies involved in these flows will require SameSite=None and Secure even if under 2 minutes old.

まとめ

今回は Chrome の SameSite 属性仕様によるアプリケーションの不具合事例を紹介しました。 過去に SameSite 属性の仕様変更があった際にこの2分の仕様が色々な記事で投稿されていましたが、今回の件で自分が詰まったときになかなかそれらの記事に辿り着けませんでした。さまざまな技術動向にアンテナを貼ることは大事だと改めて感じました。 今回の記事が自分のような人に届けば幸いです。

参考URL