質問やフィードバックがありましたら、フォームからお願いします
本文は台湾華語で、ChatGPT で翻訳している記事なので、不確かな部分や間違いがあるかもしれません。ご了承ください
ユーザーのプライバシーへの関心が高まる中、各サービスも徐々に自社のプライバシーとセキュリティを調整し始めています。例えば、MacがCatalinaにアップデートされた後、xxxが特定のリソースにアクセスすることに同意するかどうかをしつこく尋ねてきます。
セキュリティは確かに厳格になったものの、そのために時には厄介な問題が発生することもあります。たとえば、WACOMの手書き板が正常に使用できないといった事例です。
また、Googleは2019年のGoogle I/O大会でセキュリティポリシーの調整を発表し、現在のウェブ開発に最も影響を与えるのは、Chromeが徐々にサードパーティのCookieのサポートを削除することです。
Chrome 80以降では、Cookie内のsamesite
属性がデフォルトでlax
に設定されます(バージョン80ではNoneがデフォルト)。これから、samesite
の定義やその用途、Cookieについての考察をもとに、私の見解を述べていきます。
Cookieとは?
Cookieは、クライアント側で実装される小型のストレージメカニズム(4KB)で、サーバー側から返されるレスポンスヘッダーにより制御されます。これまでCookieを使用する場合、全体のメカニズムは通常ブラウザ側に依存しており、ブラウザは現在の条件および設定されたヘッダーに基づいてCookieを送信できるか、いつ期限切れになるかを決定します。Cookieが期限切れでなく、条件に適合している限り、Cookieは自動的に各リクエストに含まれて送信されます。
ステートレスなHTTPリクエストに対して、Cookieのメカニズムはユーザーのデータを保持し、サーバーが現在のユーザーの状態を判断したり、追跡したりするのに役立ちます。
私が言うには、Cookieの最も便利でありながら致命的なメカニズムはこの点です。
Cookieが期限切れでなく、条件に適合している限り、Cookieは自動的に各リクエストに含まれて送信されます。
なぜこう言えるのでしょうか?一般的な状況では、<form>
、<iframe>
、<a>
、<link>
、<img>
など、デフォルトでCookieが送信されるため、以下のようなことが可能になります:
-
iframe
を使ったサードパーティの追跡- Googleアカウントにログインすると、google.comがSet-Cookieを返し、ブラウザに保存されます。
- Bサイトでウェブページを閲覧すると、Bサイトがgoogleの
iframe
を埋め込んで追跡を行います。 - iframeがCookieをgoogleに送信します。
-
<img>
を使った追跡- Googleアカウントにログインすると、google.comがSet-Cookieを返し、ブラウザに保存されます。
- Bサイトでウェブページを閲覧すると、Bサイトがページ読み込み時に
<img src="xxxx.google.com/track/pageview" />
を使って画像リクエストを送信します。 - Cookieがgoogle側に送信されて統計が取られ、現在閲覧しているサイトがわかります。
-
<a>
を使った悪用行為- Bサイトにログインします。
- このサイトの実装が悪く、ユーザーのURLが
GET /user/delete
のように見えます。 - ハッカーがあなたにメールを送り、そこに
<a href="xxx.com/user/delete">click me</a>
があります。 - あなたがクリックすると、アカウントが削除されてしまいます。
-
<form>
を使った悪用行為- Bサイトにログインします。
- 悪意のあるAサイトでフォームを記入しますが、実際にはBサイトに送信されます。たとえば、支払い情報などです。
- あなたの支払い情報がBサイトに送信され、大きな損失を引き起こします。
第3、4点は私たちがよく知っているCSRF(Cross Site Request Forgery)クロスサイトリクエスト偽造です。防御方法は1. 副作用のある操作にGETメソッドを使用しないこと、2. CSRFトークンを利用して検証し、リクエストが本当に信頼できるソースからのものであることを確認することです。
そのため、CSRF攻撃を防ぐための一般的な手法は、HTMLにCSRFトークンを追加し、サーバーが操作を実行するたびに、このリクエストにCSRFトークンが含まれているかを確認して、ソースが本当に自社のサービスであることを確保することです。
CSRFは確かにCookieメカニズムによるセキュリティ問題を解決しています。経験豊富なエンジニアなら、ステートフルなCSRFトークンのメカニズムを実装するのがどれほど面倒で、エラーが発生しやすいかを知っているでしょう(特にトラフィックが多い場合)、CSRFトークンに言及するたびに皆が困った顔をします。
実際、13年以上前から、<img>
や<link>
などのタグがCookieを送信しないように提案されていました(記事)。しかし、得られた回答は次の通りです。
ここで説明されている攻撃はよく知られており、「クロスサイトリクエストフォージェリ」と呼ばれています。ほとんどの人は、これを修正するのはウェブアプリケーションの責任であり、ウェブブラウザの責任ではないと考えています。
CSRFによる問題を解決するために、Chrome 51+ではSameSite属性が追加され、CSRF攻撃を防ぐことができるようになりました。その原理は、Cookieを送信するかどうかの選択権を実装者に渡すことで、3つの値が選べます。
strict
どんな状況でもCookieを送信しません。最も安全ですが、時にはそうしたくないこともあります。たとえば、BサイトからYouTubeにリンクすると、Cookieが送信されないため、未ログインの状態になります。lax
GETで目的のURLにナビゲートする場合のみCookieを送信します。none
デフォルトでCookieを送信します。
Chrome 80+のCookie SameSiteポリシー
最近大きな話題となっているsamesite cookieは、Chrome 80以降、SameSite=none
のデフォルトがSameSite=lax
に変更され、ユーザーの安全性が確保されることを指します。
さて、これが背景の紹介です。次に私の考察についてお話しします。
考察 1: だからSameSite=lax
を追加すればCSRFトークンを実装しなくていいのか?
SameSiteという標準は比較的新しい概念です(実際には新しくはありませんが)、もしユーザーが古いブラウザを使用していて、samesiteがサポートされていない場合、CSRF攻撃を受ける可能性はないのでしょうか?
このポリシーの変更は私にとって少し無駄に思えます。サードパーティの追跡を防ぐことはできますが、自社サービスに対してはCSRFトークンを実装しなければ攻撃を効果的に防ぐことができません。
実際、Cookieの実装は各ブラウザ間で異なり、Cookieによるセキュリティ問題も少なくありません。たとえば:
- Safari 12以前はJavaScriptでhttpOnlyのCookieを書き換えることを許可していた
- 規約が複雑(domain=.example.comとdomain=example.comの違いは何でしょうか?)
- 同一ドメイン間でCookieを共有
- (続きは後で補足...)
要するに、ブラウザのメカニズムを利用してCookieを実装するのは、必ずしも便利ではありません。そこで、別の方法を考えました。これが考察2です。
考察 2: もしCookieをできるだけ使わない場合は?
ネットで調べてみると、こちらの記事(Cookies Are Bad for You)が見つかりました。ここで提案されている方法は非常に参考になると思います。
重要なのは、ブラウザではなくウェブアプリケーションが制御するメカニズムを選択することです。
Cookieを使う場合、ブラウザのメカニズムに依存する必要があるので、思い切ってCookieを使わず、全ての実装をJavaScriptに任せることができるかもしれません。これはどういうことかというと:
- JavaScript内で
fetch
のcredentials: include
を使ってCookieを送信するかどうかを決定することができ、CORSヘッダーと組み合わせることもできます。 - JavaScriptを使うことで、CSRF攻撃を効果的に防ぐことができます(詳細は後述)。
- 各ブラウザの実装に依存しない。
CSRF攻撃を避けるための方法として、ユーザーの身元が必要なリクエストには必ずリクエストヘッダーを追加するように要求することができます。たとえば、Authorizationヘッダーなどです。これによりCSRF攻撃を回避でき、CSRFトークンのメカニズムを実装する必要がありません。
フォームを送信する際も、ブラウザのメカニズムを直接使用するのではなく、JavaScriptのAPIを使って行います。
const form = new Form()
form.append('keyA', 'valueA');
fetch('/my-api', { body: form, method: 'POST', headers: { 'Authorization': 'xxx' } })
ただし、JavaScriptを利用するやり方では、期限管理やリクエストを自分で送信する必要があるだけでなく、以下の点も考慮しなければなりません:
- データはどこに保存する?
- XSSが発生した場合はどうする?
- ユーザーがJavaScriptをオフにした場合はどうする?
データはどこに保存する?
データ(アクセストークン)は、メモリ、localStorage
、sessionStorage
、さらにはindexDB
に保存できます。最初に聞くと少し奇妙に思うかもしれませんが、XSSが発生した場合はどうするのでしょうか?それについて考察を続けます。
まず、クライアントにあまり多くの機密情報を置かないようにし、アクセストークンの有効期限もできるだけ短く設定して、トークンが漏洩した際の影響を最小限に抑え、リフレッシュトークンのメカニズムを通じてユーザー体験を確保します。
XSSが発生した場合はどうする?
私は、あらゆるメカニズムには一定のリスクが伴うと言いたいです。Cookieも安全上の脆弱性が指摘されたことがあります。フロントエンドフレームワークの助けを借りて、ほとんどのXSSの脆弱性を回避できるかもしれません。
ユーザーがJavaScriptをオフにした場合はどうする?
JavaScriptをオフにすることは、選択肢のひとつだと思います。Facebook、YouTube、Netflixなど、これらのサービスはすべてJavaScriptを有効にすることを要求しています。
スクリーンリーダーに関しては、今後、より多くのスクリーンリーダーが基本的なJavaScriptを取り入れて、より良い体験を得ることになるでしょう。最近の傾向では、JavaScriptが数百KBの大きなパッケージになることが多いですが、スクリーンリーダーやアクセシビリティの面では、JavaScriptを使用することで、より繊細な操作が可能になります。
そうすると、img
やform
などもJavaScriptでAPIを呼び出す必要がありますか?今のSPAの時代においては、ますます多くのサービスがXHRを使ってAPIを提出しています。ただし、JavaScriptを使う必要があり、処理を書く手間が少し増えますが、より信頼性の高いセキュリティを手に入れることができます。
考察 3: OAuth
この記事で言及されている内容です。
OAuthプロトコルを通じて、認証サーバーとトークンを交換し、トークンをクライアントに保存して使用し、さらにHMACアルゴリズムを通じてリクエストメソッドとリクエストURLが正しいことを確認します。
結論
私自身、安全性と利便性は常に表裏一体であると考えています。すべての実装、認証、期限管理などのメカニズムをサーバー側で行うのは面倒ですが、以前はCookieが非常に安全で便利だと思っていました。このポリシーの変更を見て、過去のさまざまな事例を振り返ることで、再度考えを改めるきっかけとなりました。この記事の中には、当初考慮しなかった視点があるかもしれませんので、ぜひご指摘いただければと思います。
この記事が役に立ったと思ったら、下のリンクからコーヒーを奢ってくれると嬉しいです ☕ 私の普通の一日が輝かしいものになります ✨
☕Buy me a coffee