はじめに
こんにちは。セキュリティエンジニアの@okazu_dmです。
皆さんはブラウザにおいてLocal StorageやCookieに格納されている値が暗号化されているかどうかを考えたことはあるでしょうか。これらWebサービスの認証・認可において使われるデータが、XSSのようなアプリケーションの脆弱性への耐性に差があるかどうかは頻繁に議論されるところです。
しかし、ブラウザに保存されたデータが暗号化されているかどうかはまた別の攻撃経路への耐性の話であり、馴染みがないのではないでしょうか。
これは、基礎知識としてLocal StorageとCookieの仕組み/挙動の紹介と比較をしつつ、Google Chromeにおけるそれらの暗号化の実装上の違いを検証する記事です。
なお、保存先のストレージとAuth0のアクセストークンのXSS耐性との関連は過去に別の記事で検証しています。興味がある方は是非ご覧ください。
2つのブラウザ上ストレージの紹介
ブラウザ上で動作するストレージはいくつかありますが、恐らく一番よく使われるものがCookie、その次がLocal Storageでしょう。
一方で使われなくなったストレージもあり、例えばWeb SQLは、現状は仕様の整備が止まっており、実質的には廃止されたような扱いとなっています。
以下では、CookieとLocal Storageを比較してストレージとしての違いを紹介してみます。
Cookie
Cookieは本来ステートレスなプロトコルであるHTTP上で状態を管理するための機構としてRFC6265で仕様が定義されています。
大まかな仕組みとしては、
- HTTPのレスポンスヘッダによって(またはJavaScriptから直接)ブラウザ上に値を保存
- オリジンが、Cookieを保存する際に指定されたドメイン配下のものである場合は、ブラウザがリクエストを送る際に、保存されている値をリクエストヘッダとして自動的に付与する
という形です。また、Cookieは一部の条件を満たせばJavaScript内でも参照可能です。
加えて、セキュリティに関する補助機構について、一部を以下に紹介します。
- JavaScriptなどHTTP通信以外の経路から(ユーザが意図しない形で)直接操作できないようにするHttpOnly属性
- https1での通信でのみサーバに対して値を送信するSecure属性
SameSite属性など、近年のウェブサイトの仕組みと関連して非常に重要な仕組みもあるのですが、簡潔に紹介することが難しいため省略します。
その他の参考文献:
Local Storage
Local Storageの仕様は、HTML Standardの中で定められており、Cookieと同じくkey-value型のstorageとしてのインタフェースを持っています。また、Cookieとは違いHTTPのプロトコルとの直接的な紐付けはなくリクエスト/レスポンスと同時に読み書きをする仕組みはありません。
参考: https://developer.mozilla.org/ja/docs/Web/API/Window/localStorage
CookieとLocal Storageの比較
CookieとLocal Storageの違いは前述したように、値を読み書きするタイミングが違う、という点とは別に、以下のような違いがあります。
- 容量の違い: 実装に依存する箇所ですが、一般的にCookieは4KB、Local Storageは5MBと大きく差があります。
- そもそもCookieは毎回リクエストに付与して送信する都合上、あまり大きなサイズのデータを入れる用途には適さないという側面もあります。
- expireの有無: Cookieは指定した時間が過ぎたら値を破棄するようにブラウザに対して指示する機構がありますが、Local Storageにはそのような機構は存在しません。
- ドメイン指定の可否: Cookieは前述したように保存のタイミングでドメインを指定でき、それによって保存した値にアクセス可能な範囲を制御できますが、Local Storageは保存したオリジン上でのみアクセス可能です。(すなわちスキーマ/ドメイン/ポート番号がすべて一致している必要があります)
Google Chromeにおける実装上の違い
「Google ChromeのCookieは暗号化されている」という話を聞いて検証してみたことが今回の記事を書く発端となったのですが、以下で実際に検証した内容について紹介します。
簡単な検証内容の紹介としては、Linux(Ubuntu)/macOS上でGoogle Chrome(LinuxではChromiumで確認)が保存したCookieとLocal Storageをそれぞれファイルとして確認しました。その結果、Cookieの値は暗号化されていました。(しかしながらLinuxにおいては復号のためのキーが固定のようであり2、意味のある暗号化は検証した範囲だとmacOS版のみと思われます)
一方で、Local Storageについては平文でした。
検証手順の概要
まず、Cookieについては以下の記事と同じ手順で検証し、暗号化されていることを確認できました。(2023/01に検証しました)
一方で、以下の手順でLocal Storageについて検証したところ、Local Storage上の値については暗号化されていないことが確認できました。
Local Storageの検証手順
Google ChromeのLocal StorageはLevelDB というkey/value型のデータベースで管理されている、という前提知識を踏まえて検証手順を紹介します。
① 以下の画像のように、localhostで適当な値でLocal Storageに値をセットします(Cookieについてもセットしていますが、これは前述した参考記事の手順で確認する目的です)。
以下は、実際に値がセットされているかをChromeのdevtool上で確認した結果です。
② 以下の手順でPC上のファイル(LevelDB)でLocal Storageに値が書き込まれているかを確認します。
- まず、準備として以下のrubyスクリプトを
read_localstorage.rb
という名前で準備します。
require 'leveldb' db = LevelDB::DB.new(ARGV[0]) # db.keysでLocal StorageのlevelDB上のキー一覧が返ってくるので、それを確認して以下の行を書いた p db["_http://localhost\x00\x01testKey"] # "\x01testValue" が返ってくる
- 次にruby自体をセットアップし、leveldb-rubyをインストールします(ruby 3.1.1/leveldb-ruby 0.15で確認しました)。
ruby read_localstorage.rb LocalStorageのディレクトリのパス
で実行すると、実際に最初の手順でセットしたkey/valueの組み合わせでセットされていることがわかります。この際、特に復号の手順を踏んでいないことからこれは平文であることも確認できたものとします。
参考までに、今回の検証においてLocal Storageのファイル群が存在したディレクトリのパスは以下の通りです。
- macOS:
~/Library/Application Support/Google/Chrome/Default/Local Storage/
- Linux:
~/snap/chromium/common/chromium/Default/Local Storage/
考察
セキュリティ面においてCookieとLocal Storageをファイルとして比較した際、暗号化されているCookieの方が、直感的にはより安全に思われます。具体的に検討すると、「攻撃者がユーザのPC上のファイルを読み書きできる場合、攻撃者はLocalStorageのデータを取得できる。一方でCookieのデータを取得したい場合は、攻撃者はさらに管理者権限や攻撃対象のユーザのパスワードを取得する必要がある。」といった形で、攻撃成立条件に差異があることがわかります。(macOSの場合)
このため、具体的には通常のユーザ権限で動くマルウェアの攻撃に対しての耐性はCookieの方が強い、と言えそうです。一方で現在の攻撃のトレンドなどを考慮するとファイルとしてのセキュリティ強度の違いがどの程度被害を防げているか、という部分については議論の余地が大いにあると考えています。
まとめ
この記事では、CookieとLocal Storage、それぞれについての概要の説明と大まかな違い、またGoogle Chromeにおける実装上の違いについて紹介しました。
一点注意をしていただきたいのは、保存時にCookieが暗号化されている、というのはあくまでGoogle Chromeの実装上はそうなっているだけ、という点です。そのため、この挙動を期待したアプリケーションを作ることは避けたほうが良いでしょう。
Flatt Security ではWebアプリケーションをはじめとする、様々なプロダクトへのセキュリティ診断サービスを提供しています。仕様・実装に不安のある方はぜひお気軽にお問い合わせください。
上記のデータが示すように、診断は幅広いご予算帯に応じて実施が可能です。ご興味のある方向けに下記バナーより料金に関する資料もダウンロード可能です。
また、Flatt Security はセキュリティに関する様々な発信を行っています。 最新情報を見逃さないよう、公式 Twitter のフォローをぜひお願いします!
Tweets by flatt_security twitter.com
では、ここまでお読みいただきありがとうございました。
謝辞
この記事は@shioshiotaとの会話に着想を得て書かれました。この場を借りてお礼を申し上げます。
- RFC6265には、どの経路を安全と定義するかはブラウザ側の責務であるとする記述がありますが、執筆時点ではhttpsでの通信のみ、としても問題ないと判断しています↩
- https://www.reddit.com/r/netsec/comments/39swuj/key_for_chromiums_encrypted_cookies_store_in/ 過去のユーザ間の会話ですがこのようなログも見つかりました↩