前言
2019年、私はMediumにCookieとCORSに関する記事を投稿しました(CookieとCORSの扱い方)。好評を得て、記事は海外でも引用されました。皆さんは原作者がここにいることを覚えておいてください。その記事では、CORSの概要とフロントエンドとバックエンドでの正しいCORSの実装方法について議論しました。これはウェブ開発においてよく遭遇する問題です。
1年以上経った今、ChromeはCookieポリシーの変更(SameSiteのデフォルトをlaxに変更)を発表しました。私はこのことを再考し、ChromeのCookieポリシーの調整と考察という記事で、SameSiteについての疑問について述べました。まだ2つの質問について答えを見つけていません:
- サーバーにSameSiteを追加した場合、CSRFメカニズムの実装は不要になりますか?古いブラウザ(SameSite属性をサポートしていない)を使用してセキュリティ上の問題が発生した場合、このセキュリティ上の問題は企業の責任なのか、ブラウザの責任なのか、教えていただけますか?
- Cookieは本当に完全な方法なのでしょうか?
最初の質問については、時代の進化に伴い、ますます多くのブラウザが時代に追いつくと思われますので、その時には完全にCSRFメカニズムを廃止できるのではないでしょうか?
2つ目の質問は、いくつかのケースを見たことから生まれた質問ですので、ここで詳しく説明します:
それでは、まず小さなテストを行いましょう。cookieにdomain=.example.comとdomain=example.comを設定した場合、それぞれの範囲はどうなりますか?
答えは後で見てみましょう。まずはクラシックなケースを見てみましょう - Cookie Bomb
1. Cookie Bombとは?💣
この記事では、Cookie BombをデモンストレーションするためにGitHub Pageを使用しています。
GitHubはコードを置くだけでなく、GitHub Pageとして個人のウェブサイトやブログを作成することもできます。GitHubはuser.github.io
というドメイン名も提供しています(以前はusername.github.com
でしたが、この記事を参照してください)。
通常、1つのCookie(キー、値、有効期限などを含む)には4KBのデータを格納でき、最大で50個まで格納できます(ブラウザによって異なる場合があります)。したがって、合計で最大4KB * 50 = 200KBまで格納できます。Cookie Bombの手法は、クライアントサイドでdocument.cookie
を使用して大量のCookieを書き込むことです。Cookieはデフォルトですべてのリクエストに含まれるため、200KBはGETリクエストにとって非常に大きいです。そのため、一般的にはサーバーサイドまたはnginxレベルでリクエストがブロックされ、無視されます。
そして、最大の問題がここにあります。Cookieは自動的にすべてのサブドメインに適用されます(一部の条件を除く)。したがって、Aページで被害を受けた場合(Cookieでいっぱいになった場合)、BページやCページでも同じように影響を受けます。これにより、すべてのGitHub Pageが壊れたように見えます。
厳密に言えば、このCookie Bombはセキュリティ上の問題を引き起こしませんが、ちょっとしたいたずらにすぎません。被害者は大量のCookieを削除するだけで、リクエストが正しく処理されるようにすることができます。ただし、このようなブログサービスのCookieが共有される状況は少し奇妙です。
この問題を回避するために、特定のドメイン内のサブドメインではCookieを共有しないようにすることができます。これはpublic suffix listと呼ばれ、Mollizaによって維持されており、すべてのパブリックサフィックスドメインのリストを取得できます。
さて、先ほどの質問に戻りますが、domain=.example.com
とdomain=example.com
は同じです。両方の場合、すべてのサブドメインに適用されます。
domainが設定されていない場合、少なくともデフォルトでは同じドメインに制限されます。設定されている場合は、Cookieはすべてのサブドメインに送信されます。
2. Cookieの動作原理を本当に理解していますか?
現在のブラウザはおそらくRFC6265の仕様に従ってCookieを実装しているはずです。たとえば、Cookieの値が同じ場合の処理方法や、Cookieのソートが必要かどうかなどです。
私の経験では、Cookieの奇妙な歴史を理解し、仕様の詳細を知っているエンジニアは非常に少ないと思います。これは非常に普通のことだと思います。一般的なエンジニアは、Cookieの設定方法、httpOnly
やsecure
の動作方法、パスやドメインのロジックについて大まかな知識を持っています。製品を作るためには、すべての仕様書を読んでCookieの経緯を理解する必要はありません。
上記のすべてが成功裏に防護されている場合でも、使用しているフレームワークがCookieをどのように解析・実装しているかを確認する必要があります。
再考:別の可能性
これらのケースを考慮した結果、最適なアプローチは何かを考え始めました。その結果、焦点をJavaScriptに当てることになりました。次に、JavaScriptでこれらの問題をどのように解決しているかを見てみましょう。
私は以前の記事でも触れましたが、参考にした記事はこちらです:Cookies are bad for you。ここで少し復習します。
JavaScriptによって送信されるリクエスト(ajaxやfetchなど)は、CORSリクエストと定義されます。CORSリクエストでは、いくつかの条件と制限があります。詳細については、以前の記事やMDNを参照してください。ここでは、CORSがリクエストをどのように保護しているかについて説明します:
- クロスドメインリクエストでは、デフォルトではCookieは自動的に送信されません:これによりCSRFが防止されます。CORSリクエストはJavaScriptコードから送信されるため、通常のCSRF攻撃は機能しません。
- CORSではJavaScriptの実行が必要です:認証メカニズムを
Authorization
というリクエストヘッダに配置できます。一般的なフォームやリンクの攻撃はリクエストヘッダに追加することができないため、CSRFを効果的に防ぐことができます。 - リクエストにヘッダを追加できます:fetchを使用してヘッダを動的に追加して認証メカニズムを行うことができます。例えば:
fetch('/api', {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token')
}
})
- 認証がCookieメカニズムを使用しない場合:複雑なCookieの仕様を理解する必要がなくなり、CSRFの実装問題を心配する必要がありません。
さて、皆さんが一番知りたいことです:
token
をlocalStorage
に保存すると、XSS攻撃で危険ではありませんか?- ユーザーが
JavaScript
を無効にしている場合、機能しなくなりませんか?
問題1については、私自身もまだ考え中ですが、誰もが答えてくれないようです。「では、Cookieを使用してXSSが発生した場合、問題は発生しないのでしょうか?」
違いは攻撃者がトークンを取得できるかどうかです。XSSが発生した場合、次のようなことが起こります:
- Cookie:XSSが発生 → 攻撃者が悪意のあるコードを挿入 → データを抽出
- localStorage:XSSが発生 → 攻撃者がトークンを取得 → データを抽出
私は現在の議論がすべて2番目のステップに留まっていることに気付きましたが、「では、結果が同じであるなら、CookieとlocalStorageの本質的な違いは何ですか?」という点については誰も議論していないようです。誰もがCookieを絶賛し、安全だと思っており、localStorageを非難していますが、実際にはそうでしょうか?Cookieは本当に完全に安全なのでしょうか?仕様の複雑さがより多くの潜在的な問題を引き起こしている可能性はありませんか?私はこの点についてはあまり多くの議論を見ていません。
問題2については、localStorageの仕組みを利用する場合、もちろんいくつかのコストを負担する必要があります。実際、ほとんどのユーザーはJavaScriptを有効にしてウェブページを閲覧するでしょう。JavaScriptを実行できない場合、SSRを実装していないSPAウェブページは正常に動作しなくなるでしょう。
後記
私はCookieについていくつかの疑問を持っており、フレームワークの実装が十分に堅牢であるかどうかわかりませんが、Cookieの仕様は今ではかなり安定しており、安全性も向上していると思います。フレームワークの場合、オープンソースであれば信頼できるはずです。SameSiteの実装はほとんどのブラウザで使用できるため、将来性があると思いますが、さらなる議論と刺激を得ることを望んでいます。そのためにこの記事を書きました。
以上が私自身のCookieに対する理解です。もし間違いがあれば指摘してください。
補足
記事がグループで公開された後、いくつかの提案があり、コメントセクションで確認できます。