この記事は CBcloud Advent Calendar 2019 19日目の記事です。
CBcloud 徳盛です。
HTML5のLocal Storageを使ってはいけない(翻訳)やAuth0のドキュメント内のWhere to Store Tokens
に記載されている通り、LocalStorageに保存するのはアンチパターンだと認識してはいますが、その理由を自分の言葉で説明しろと言われると無理だったので調べてみました。
CORS時のCookieの挙動をある程度理解する
まずCookieとは
サーバがブラウザに送信する小さなデータです。この情報はブラウザに保存され、次のリクエスト時に保存されたデータがサーバへ送信されます。
詳しくはこの辺りを参照してください。
・HTTP Cookie - HTTP | MDN - Mozilla
・HTTP Cookieとは:超入門HTTP Cookie - @IT
挙動を調査 (SPAで他のドメインのAPI呼び出すときとか)
詳細はオリジン間リソース共有 (CORS) - HTTP | MDNに載っているので参照してください。
気をつける点としては以下の4つかと思います。
- リクエスト送信時にwithCredentials=trueをしないとset-cookie情報があってもブラウザがCookieをセットしてくれない
- withCredentials=trueをしても、サーバ側でAccess-Controll-Allow-Credentials=trueをレスポンスヘッダにセットしないと、ブラウザがレスポンスを処理してくれない。ただし、Cookie情報は保存される。
- Access-Control-Allow-Originヘッダーにワイルドカードを指定できない
- ブラウザの設定でサードパーティCookieを拒否している場合、set-cookie情報があっても保存されない(Safariはデフォルトブロック)
ちなみにCookieを確認する際は、Chrome拡張等のCookie参照ツールを使うのをお勧めします。はじめChromeのdevtoolで確認していましたが、xhrの実行元ドメインに登録されるのかと勘違いしていて、気づくのに時間を要してしまいました。そりゃそうですよね。。
念の為、LocalStorageとは
Web Storageの一種で、Cookieよりも直感的にキーバリューの値をブラウザに保管することができます。
JavaScriptを利用して更新や取得をおこないます。
それぞれのリスクについて
リスクに関連する両者の大きな挙動の違いとしては、javascriptからのアクセスを拒否できるかどうかと勝手に送信してしまうかどうかです。
type | jsからのアクセス | 自動送信 |
---|---|---|
Cookie | 不可(http-only属性による) | する |
LocalStorage | 常に可能 | しない |
Cookieはhttp-only属性を利用することでjsからのアクセスを不可にすることが可能ですが、LocalStorageはそもそもjsで利用する前提のため当然できません。
また、Cookieはドメインが一致していれば自動でアクセス先にデータを送信してしまいますが、LocalStorageは送信しません。
これらの違いを踏まえ、CSRFとXSSの脆弱性について考えていきたいと思います。
3分でわかるXSSとCSRFの違い
Cookieに保存した場合のリスク
自動で送信してしまうCookieの挙動を利用した攻撃に、クロスサイトリクエストフォージェリ(CSRF)があります。
クロスサイトリクエストフォージェリ(CSRF)とは、Webアプリケーションに存在する脆弱性、もしくはその脆弱性を利用した攻撃方法のことです。 掲示板や問い合わせフォームなどを処理するWebアプリケーションが、本来拒否すべき他サイトからのリクエストを受信し処理してしまいます。
クロスサイトリクエストフォージェリ(CSRF) | トレンドマイクロ
セッションの保存先をCookieにしたSPA実装時のAPIサーバ側でCSRFを防ぐためには、CORSのAccess-Control-Allow-Originを正しく設定しただけでは不十分です。
あくまでブラウザがレスポンスをブロックするだけで、サーバ側で処理自体はされてしまうためです。
簡単な対応策として、独自ヘッダを通信時に付与することでプリフライトリクエストを強制的に発生させることで、そのレスポンスをブラウザにブロックさせ、その後のメイン処理をさせない手法があります。
ただし、推奨はされないとのこと(徳丸さんのtwitter下記参照)。
@makotokuwata こんにちは。ぎりぎり大丈夫そうですが、ちとマージンが少ないの不安です。XHR Level2でリクエストを送れる(レスポンスは受け取れないがCSRFには十分)からです。独自ヘッダをつけるとプレフライトリクエストが飛ぶので、応答しなければ大丈夫ですが…
— 徳丸 浩 (@ockeghem) 2014年1月22日
この辺りはまだ何故推奨されないのか理解できていないため、今後調査していきます。
ちなみにプリフライトリクエストは以下の内容を参照してください。
「単純リクエスト」 (前述) とは異なり、「プリフライト」リクエストは始めに OPTIONS メソッドによる HTTP リクエストを他のドメインにあるリソースに向けて送り、実際のリクエストを送信しても安全かどうかを確かめます。サイト間リクエストがユーザーデータに影響を与える可能性があるような場合に、このようにプリフライトを行います。
オリジン間リソース共有 (CORS) - HTTP | MDN
LocalStorageに保存した場合のリスク
LocalStorageはJavaScriptからアクセスが可能なため、XSSを利用したセッションハイジャックを考慮する必要があります。
クロスサイトスクリプティングとは、ユーザのアクセス時に表示内容が生成される「動的Webページ」の脆弱性、もしくはその脆弱性を利用した攻撃方法のことです。動的Webページの表示内容生成処理の際、Webページに任意のスクリプトが紛れ込み、Webサイトを閲覧したユーザ環境で紛れ込んだスクリプトが実行されてしまいます。
クロスサイトスクリプティング(XSS) | トレンドマイクロ
最近はAngular/Vue.js/React等のframeworkを利用することが多いため、自力でDOMをレンダリングすることがほぼなくなりました。そのため、使い方に気をつければ、それほど気にしなくてもXSS対策はある程度できているはずです。
ただし、どれだけXSS対策を完璧にしていたとしても、HTML5のLocal Storageを使ってはいけない(翻訳)中に記載されている、第三者のJavaScriptコードが悪意のある第三者乗っ取られることが仮にあるとしたなら、自分達だけでの完璧な対策は無理かと思います。
まとめ
どちらも結局のところ、しっかりとした脆弱性対策をする必要があり、それ単体のみではセッションIDは安全ではありません。
ただ、今の所、JavaScriptからの攻撃を防げないという点でCookieを選ばざるおえないという判断なのでしょうか。
CORS時の保存先にCookieを選択する場合、サードパーティCookieとして認識されてブラウザにブロックされる可能性があることも頭の中に入れておく必要がありそうです。
この記事の中で間違いやこんな内容をもう少し書いてもらいたいなどありましたらお気軽にコメントお願いします!