こんにちは、 @okazu_dm です。
この記事は、CookieのSameSite属性についての解説と、その中でも例外的な挙動についての解説記事です。
サードパーティCookieやCSRF対策の文脈でCookieのSameSite属性に関してはご存知の方も多いと思います。本記事でCookieの基礎から最近のブラウザ上でのSameSite属性の扱いについて触れつつ、最終的にHSTS(HTTP Strict Transport Security)のような注意点を含めて振り返るのに役立てていただければと思います。
- 前提条件
- Cookieについて
- Cookieの属性について
- SameSite属性について
- SameSite属性に関する落とし穴
- ではどうすればよいのか
- まとめ
- お知らせ
- Flatt Securityは外部パートナーと連携して技術記事を発信しています
前提条件
この記事の中でいくつかの実験を行っていますが、それぞれ2023年12月時点のMac OS上でChrome、Firefox、Safariの3つのブラウザの最新版で検証しました。
Cookieについて
まず、前提としてCookieについて簡単に紹介します。(このあたりの基礎知識は、MDNのサイト の記述が最も分かりやすさと網羅性のバランスが良いです)
Cookieは、本来状態を持たないプロトコルの中で、ブラウザにHTTPコネクションを越えて状態を保持させるための仕組みです。 HTTPのレスポンスヘッダ、 リクエストヘッダによりCookieに保存された文字列がやりとりされます。具体的にはサーバからSet-Cookieレスポンスヘッダを通じてブラウザにセットされ、逆にブラウザからはCookieリクエストヘッダを通じてサーバに対して送信されます。
上の画像ではSESSION_IDという名前のCookieにサーバサイドのアプリケーションが発行したランダムなセッションIDをセットしています。Cookieの上書きや削除、ブラウザ側の機構により判定される例外などいくつかのケースを除いて、これ以降の通信ではブラウザからサーバに対してCookieヘッダを通してこのCookieが渡されます。
このようにすることで、サーバサイドアプリケーションは一連の通信をするブラウザを見分けることができます。
Cookieの属性について
Cookieには利便性やセキュリティ強化の用途で様々な属性が用意されています。それぞれサーバサイドアプリケーションが、Set-CookieヘッダにCookieの名前や値と一緒に付加してブラウザに対して渡します。RFCに含まれないものもありますが、この記事の内容に関係あるものは以下の属性です。
- Secure属性: httpsの通信でのみCookieを送信する(中間者攻撃対策)
- HttpOnly属性: JavaScriptで document.cookie からこの属性が付加されたCookieを操作することはできない(XSS攻撃に対する緩和策)
- Domain属性: ブラウザは、この属性で指定されたドメイン以下(サブドメインを含む)に、この属性が付加されたCookieを送信する。Domain属性がない場合は、Cookieがセットされたドメインに対してのみCookieを送信する(サブドメインを含まない)
- Path属性: ブラウザは、この属性で指定されたパス配下(サブディレクトリを含む)に、この属性が付加されたCookieを送信する。
- SameSite属性: サイト間通信における挙動を制御する。詳細は後述します。
またCookieの名前の頭に特定の文字列をつけることで、属性に関する制約を強制するCookie Prefixesという仕様が提唱されており、それぞれ以下のような内容です。
- __Secure- prefix: Set-Cookieヘッダでサーバから渡されたCookie名が
__Secure-
から始まる場合は、以下の条件をすべて満たす場合のみブラウザは受け入れる- Secure属性が付加されている
- httpsの通信である(URIスキームが安全なものである)
- __Host- prefix: Set-Cookieヘッダでサーバから渡されたCookie名が
__Host-
から始まる場合は、以下の条件をすべて満たす場合のみブラウザは受け入れる。- Secure属性が付加されている
- httpsの通信である(URIスキームが安全なものである)
- Domain属性が付加されていない
- Path属性の値が
/
__Host- prefixは条件だけだとややわかりにくいですが、挙動としては「セットされた通信先のドメインと同一のドメインに対してしか送らず、そのドメインに対してはパスに関わらず送る」といったものです。
ここで紹介したCookieの機構は一部ですが、この中でもSameSite属性について詳しく解説します。
SameSite属性について
まず、SameSite属性の話をする前に”Site”という用語についてMDNの解説に軽く触れます(後ほど、より詳細に触れます)
この記事中では、”Site”や”サイト”と表記した場合、URL中のPublic Suffix(eTLD)とその手前のドメイン名(eTLD+1)までの部分を指します(例えば https://foo.website.example
というURLのサイト部分は website.example
)。
以上を踏まえ、SameSite属性についての解説をします。
SameSite属性はサイト間通信においてCookieをどのように扱うかを制御するための属性です。値には、以下の3パターンがあります。
- Strict: 送信元のサイトとCookieがセットされたサイト(送信先のサイト)が同じ場合のみ送信される。つまりサイト間通信では一切Cookieは送信されない。
- Lax: GETリクエストやmethod=GETのフォームによる画面遷移のようなトップレベルナビゲーションの場合のみ、サイト間通信であっても送信先サイトで設定されたCookieは送信先に渡される。
- None: 制約がない状態。JavaScriptによる通信や、imgタグによる参照など、Laxだと制限されているサイト間通信であっても、送信先サイトで設定されたCookieは送信先に渡される。
- ただし、Secure属性を同時に付加する必要があり、Secure属性がない場合は以下のようにブラウザ側は受理しないことが仕様とされている。ちなみに、2023年12月現在FirefoxとSafariは、(それぞれ別の緩和策があるものの)この仕様を実装していない。
- ただし、Secure属性を同時に付加する必要があり、Secure属性がない場合は以下のようにブラウザ側は受理しないことが仕様とされている。ちなみに、2023年12月現在FirefoxとSafariは、(それぞれ別の緩和策があるものの)この仕様を実装していない。
SameSite属性の目的の一つとして、CSRF(Cross-Site Request Forgeries)の対策が挙げられます。
CSRFは悪意ある攻撃者が正規のユーザに対して、(ユーザに気づかれないように偽装した上で)攻撃対象のサイトに向いたリンクをクリックさせる、フォームを入力させるなどのアクションを取らせることで、攻撃対象のサイト上で退会やメールアドレスの変更など特定の行動を強制する攻撃です。
例えば、以下の図に示す攻撃者が用意したWebサイト上には「今すぐ10万円クーポンをGET!」というボタンがあります。このボタンをユーザがクリックすると攻撃者が用意したフォームの内容が送信され、攻撃対象となるWebアプリケーション(正規Webサイト)に対しメールアドレス変更リクエストが送信されます。
ここで、正規Webサイト上でユーザのセッションIDを記録するCookieにはSameSite属性が設定されていないものとします。この時、正規Webサイトにログイン済みである正規ユーザが攻撃者のWebサイトにアクセスし、ボタンをクリックしてしまうと、ユーザのブラウザが正規Webサイトに対してメールアドレス変更リクエストを送信し、これが受理されてしまいます。当然、変更後のメールアドレスは攻撃者が任意に指定できるものです。
こうして、攻撃者が正規Webサイト上の正規ユーザのメールアドレスを自分の好きな値に書き換えることに成功してしまいました。
しかし、正規WebサイトのセッションIDを保持するCookieにおいて、SameSite属性がStrictにセットされている場合は、そもそも攻撃対象のドメインに対するサイト間通信でCookieが送信されません。そのため、正規サイト上では以下のようにサイト間リクエストが末ログイン状態と扱われ、メールアドレス変更が成功しません。
このように、セキュリティの観点からするとSameSite属性はCSRF対策として有効です。
一方でアクセス解析などの目的で、外部のアクセス解析サービスのサイトから自サイト上のユーザをトラッキングできるようにしたい場合、解析サービスのサイトに対してサイト間通信が可能である必要があります。そのため、解析サービスのサイトから発行されるCookie(サードパーティCookieと呼ばれます)はSameSite=Noneを使わざるを得ないケースもあります。このように、SameSite属性については利便性とのトレードオフという課題があります。
SameSite属性に関する落とし穴
ここまでに、SameSiteは利便性とのトレードオフという面はあれども、CSRF対策としては有効であるという話を書いてきましたが、いくつかセキュリティ対策としては気になる挙動があるため、ここではそれを紹介します。
SameSite属性を指定しなかった場合の挙動
SameSite属性を指定しなかった場合にサイト間リクエストを送信した際の主要ブラウザの挙動について紹介します。
- Chrome: デフォルトではSameSite属性はLaxと扱われるが、例外的にCookieをセットされた直後の2分はGETだけでなくPOSTによる画面遷移でもCookieが乗る(JavaScriptの通信に関しては乗らない)。
- この挙動はデフォルトをLaxにした際の後方互換性を意識したものと思われる。
- 仕組み上はこの2分間はCSRFに対してはLaxの時よりは弱い状態。
- https://developers-jp.googleblog.com/2020/02/2020-2-samesite-cookie.html
- Firefox: デフォルトではSameSite属性はNone。実際にJavaScriptでリクエストを送信した場合の挙動としては、Cookieが送信されることが確認可能。しかしこれは送信元ドメインごとにCookieのストレージが隔離されているため問題ない。
- この機能は包括的Cookie保護と呼ばれており設定画面上で無効にすることも可能。
- https://blog.mozilla.org/en/products/firefox/firefox-rolls-out-total-cookie-protection-by-default-to-all-users-worldwide/
- Safari: デフォルトではSameSite属性はNone。実際にJavaScriptでリクエストを送信した場合はCookieは送信されない。
- 設定から「サイト越えトラッキングを防ぐ」のチェックを外すと送信される。
- 設定から「サイト越えトラッキングを防ぐ」のチェックを外すと送信される。
このように、ブラウザごとにSameSite属性が指定されなかった場合の挙動は異なっており、セキュリティ上の観点でも開発時の挙動確認のしやすさ等の観点でも、極力LaxかStrictを明示的に指定することを推奨します。
SameSite: Strictでも攻撃が成功するケース
例1: スキームだけ違うケース
例えば、以下のような限定的なケースでは中間者攻撃(ユーザの通信に割り込み可能な攻撃者によって暗号化していない通信が改ざんされてしまう攻撃)によって、CSRFのような攻撃が成立してしまいます。
まず正規のユーザが"httpsの正規のWebサイト"(
https://website.example
)にアクセスして、SameSite属性がStrictに設定されたCookieを受け取る。正規のユーザがhttpのリンクを踏むなどして、最終的に"http通信に割り込まれて改ざんされた攻撃者が作成したWebサイト"(
http://website.example
) を開いてしまう。ユーザは攻撃者が作成したWebサイトを開いた瞬間に、JavaScriptによる通信で "httpsの正規のWebサイト" (
https://website.example
)にメールアドレス変更などのリクエストを飛ばす。
※このときJavaScriptが自動で発火するように仕込むことが可能なため、ユーザによる操作は必要ない。一部のブラウザはhttpsとhttpの違いはSameSite属性のコンテキストでは同一サイトとみなして、SameSite: StrictなCookieを送信してしまい、正規のWebサイトに対する意図しないメールアドレス変更リクエストが成功してしまう。
挙動の確認のために実際にCookieが飛ぶかどうかを確認したところ、FirefoxとSafariでは実際にCookieは飛びましたが、Chromeでは再現しませんでした。 これはどこまでを同じサイトとするか、という点について複数の考え方があるために発生する違いです。 これについては前述したMDN中の用語説明に基づいて解説します。
今回、ブラウザ間ではURLのスキームの部分(http/https)の扱いが違いましたが、ちょうどその部分についての記述があります。
これらは、同じサイトであったり、スキームが考慮されれば異なるサイトであったりします。
つまり、現状スキームだけが違う2つのURLを同じサイトと見なすかどうかは未定義であり、ブラウザによって対応状況が分かれているようです。 スキームまで一致したサイトを"Schemeful-SameSite"と呼ぶケースもあります。
例えばFirefoxで、httpのサイトから同じドメインでスキームだけhttpsになっているサイトにアクセスした際の挙動を確認した結果は以下のようになります。
まず、1発目のAJAXによる通信でレスポンスにSameSite: StrictなCookieが帰ってきています(/etc/hosts
で手元に向けたダミーのドメインで確認しています)。見やすさのためAJAXを利用していますが、通常通りWebサイトを利用しようとしてhttpsのサイトにブラウザでアクセスしてもこの手順と同じ結果が得られます。
そして、再度同じ内容で通信したところ、以下のようにSameSite: StrictなCookieを「要求Cookie」としてhttpsで送信してしまっています。この手順は、改ざんされたhttpのサイト上で攻撃者の用意したJavaScriptが、被害者のCookieを伴ってhttpsの正規サイトにリクエストを送信することに対応します。
このことから、Firefoxはスキームが違ってもドメインが同じであれば同じサイトとみなすことがわかります。
上記参考サイトの情報を基に各ブラウザの状況を確認すると以下のようになっています。
- Chrome:
- スキームが違うと別のサイトと見なす
- https://chromestatus.com/feature/5096179480133632
- Firefox:
- 挙動としてはスキームが違っても同じサイトとみなされる
- about:config から network.cookie.sameSite.schemeful というフラグがあるのがわかるが、これがtrueであっても挙動は同じ
- Safari:
- Firefoxと同じくスキームが違っても同じサイトとみなされる
例2: サブドメイン
上と同じくMDNの"Site"の説明からわかるように、SameSite属性等のコンテキストではURL中のeTLDとその手前のドメイン名(eTLD+1)までを"Site"と扱い、これが同じ場合は"SameSite"とされます。
例: website.example
というドメインに対しては以下のように扱われます。
www.website.example
はSameSite(サブドメインであってもeTLD+1は同じ)www.website.example:8080
はSameSite(CrossOriginではある)foo.example
やexample.com
はCrossSite(eTLD+1 がそもそも異なる)
SameSite属性でもこのロジックに基づいて、通信にCookieを付加するかどうかが判断されます。 この挙動については以下の解説も参考になります。
これらのケースをまとめて、送信元と送信先の組み合わせごとにSameSite: StrictなCookieの送信有無をまとめたのが以下の表です。
ではどうすればよいのか
SameSite属性について、以下のことを示しました。
- Noneのときはもちろん、何も指定しないときもCSRF対策としては不安が残る。
- Strictであってもエッジケースとして、中間者攻撃と組み合わせた攻撃などに関してはリスクがある。
そのための対策として、まず以下の2つは(外部からのクロスサイトリクエストが既に必須レベルになっている状態でなければ)コストが低く実現可能と思われます。
- SameSite属性についてはLax、Strictのいずれかを指定すること。
- 中間者攻撃への対策として、httpアクセスが発生しないようにHSTS(HTTP Strict Transport Security)を有効化すること。
- これにより一度サイトに来たユーザはhttpのリンクを踏んでも自動でhttpsの通信をするようになり、中間者攻撃のリスクは大幅に減ります。
- この時、サブドメインもまとめて保護するためにincludeSubdomainsもつける必要があります。
上記の二点は、httpの同じドメイン経由の攻撃と、httpのサブドメイン経由の攻撃に対しての対策としては有効です。しかし、httpsのサブドメイン経由で攻撃されるリスクに対しては、やはりアプリケーション側のロジックで、正規ユーザの意図したリクエストかどうかを判別する必要があります。
(サブドメイン経由の攻撃例: プログラムの動作を確認できるサンドボックスのようなサービスをサブドメインで動かしているケースなど)
また、ユーザ側としてはブラウザの保護機能をデフォルトより弱くしないことが重要です。
まとめ
Cookieを紹介する中で、今回はSameSite属性について踏み込みました。
SameSite属性はCSRFの対策として有用とされていますが、実際に挙動を見てみると
- ブラウザごとにデフォルト値は違う。
- ブラウザごとにSameSite: Noneのときの挙動も違う。
- ブラウザごとにどこまでを同じサイトと見なすかも違う。
というようにブラウザの実装に依存する範囲が多いことがわかります。
以上を注意点として考慮しつつ、CSRF対策としては
- 極力SameSite属性にはLaxかStrictを指定することを推奨します
- HSTS(HTTP Strict Transport Security)の有効化を推奨します
- サブドメインもSameSiteのため、includeSubdomainsも必要
- 状況によってはやはりCSRFトークンによるアプリケーション側の対策も必要になり得る
という内容でした。
お知らせ
Flatt Security ではWebアプリケーションをはじめとする、様々なプロダクトへのセキュリティ診断サービスを提供しています。仕様・実装に不安のある方はぜひお気軽にお問い合わせください。
上記のデータが示すように、診断は幅広いご予算帯に応じて実施が可能です。ご興味のある方向けに下記バナーより料金に関する資料もダウンロード可能です。
また、Flatt Security はセキュリティに関する様々な発信を行っています。 最新情報を見逃さないよう、公式 Twitter のフォローをぜひお願いします!
では、ここまでお読みいただきありがとうございました。
Flatt Securityは外部パートナーと連携して技術記事を発信しています
本稿はFlatt Securityの外部パートナーが執筆し、Flatt Securityが監修を行った記事です。通常、企業の技術ブログは自社の技術力やカルチャーの発信のため、雇用関係にある社内メンバーの執筆によって発信されることがほとんどだと思います。
一方、Flatt Securityの技術ブログでは、本稿のようにセキュリティの知見をお持ちの外部の方に依頼しているケースがあります。種々の脆弱性情報や情報発信に知見を持つFlatt Securityの技術ブログ編集部が執筆者の方と連携することで、テーマや構成の検討・レビューをサポートしています。
これは、Flatt Securityの技術ブログの目的が自社のアピールにとどまらず、セキュリティに関する有益な情報をより多く社会に還元し、セキュリティ企業とセキュリティサービス利用者の間の情報の非対称性を無くすことを目的としているためです。
本文章は執筆者がセキュリティ診断のようなFlatt Securityのサービス提供に直接的に関わっているかのような誤解を防ぐために明記していますが、執筆にご興味を持たれた方はお問い合わせください。
※Flatt Securityが技術ブログ企画において重視している考え方については、以下の記事をご覧ください。