ユーザーのあらゆる非公開 Tor アイデンティティを結び付ける安定した Firefox 識別子が見つかった
(fingerprint.com)indexedDB.databases()の返却順序だけで、Firefox ベースのブラウザにおいてプロセス寿命のあいだ維持される安定した識別子を生成可能- この識別子は origin の範囲を超えて共有されるため、無関係なサイト同士でも同じブラウザランタイム内で同一の値を観測でき、cross-origin トラッキングに利用可能
- Firefox のプライベートブラウジングでは、すべてのプライベートウィンドウを閉じたあとでもプロセスが生きていれば識別子は維持され、Tor Browser のNew Identity後も残り続ける
- 原因は Gecko の IndexedDB 実装で、プライベートデータベース名を UUID ベースのファイル名にマッピングし、その結果をソートせずに公開していたことにある
- Mozilla はFirefox 150とESR 140.10.0で修正を配布しており、内部ストレージ順序を外部に露出させない設計がプライバシー保護で重要であることを示している
脆弱性の概要
- すべてのFirefox ベースのブラウザで、
indexedDB.databases()が返す項目の順序からプロセス寿命のあいだ維持される識別子を抽出できる- Web サイトが複数の IndexedDB データベースを作成したうえで返却順序を確認すると、実行中のブラウザプロセスに固有で決定的な識別子を生成できる
- この挙動は origin スコープではなくプロセススコープで発生し、無関係なサイト同士でも同じブラウザランタイム内では同一の識別子を観測できる
- Firefox のプライベートブラウジングでは、すべてのプライベートウィンドウを閉じたあとでも Firefox プロセスが動作し続けていれば識別子は維持される
- Tor Browser では、Cookie と閲覧履歴を消去して新しい Tor 回線を使うNew Identity後でも安定した識別子が維持される
- これは、その後のブラウザ活動が以前の活動と結び付かないはずだという期待に反し、ユーザーが依存する非連結性の保証を弱める
- Mozilla と Tor Project に対して責任ある開示が行われた
- Mozilla はFirefox 150とESR 140.10.0で修正を配布した
- パッチはMozilla Bug 2024220で追跡されており、原因は Gecko の IndexedDB 実装にあるため、Tor Browser 以外の Firefox ベースブラウザにも関係する
- 修正の原理は単純
- ブラウザはプロセススコープの内部ストレージ順序を外部に露出させてはならない
- 結果を正規化またはソートして返せば、エントロピーを除去し、安定した識別子の悪用を防げる
なぜ重要か
- プライベートブラウジングモードやプライバシー重視ブラウザは、異なるコンテキスト間でユーザーを識別しにくくすることを目的としている
- 一般的な期待 1: 共有ストレージや明示的なアイデンティティ機構がない限り、無関係なサイトが同じブラウザインスタンスとやり取りしているかどうかを知るべきではない
- 一般的な期待 2: プライベートセッションが終われば、そのセッションに結び付いた情報も消えるべきである
- 今回の挙動はこの 2 つの期待をともに破る
- サイトは Cookie、localStorage、明示的なクロスサイトチャネルなしでも、ブラウザ内部ストレージの挙動だけから識別子を導出できる
- API が返すデータベース名の順序から、高容量の識別シグナルを取得できる
- 開発者視点での教訓もある
- プライバシー脆弱性は、識別データへの直接アクセスだけで発生するわけではない
- 内部実装の詳細が決定的に露出した場合でも、プライバシー漏えいは起こりうる
- セキュリティおよび製品の観点での要点
- 一見無害な API でも、安定したプロセスレベル状態を漏らせばクロスサイト追跡ベクターへと変わりうる
IndexedDB と indexedDB.databases()
- IndexedDBはクライアント側の構造化データ保存用ブラウザ API
- Web アプリケーションがオフライン対応、キャッシュ、セッション状態、そのほかのローカル保存に利用する
- 各 origin は 1 つ以上の名前付きデータベースを作成でき、object store と大容量データを保存できる
indexedDB.databases()は現在の origin から見えるデータベースメタデータを返す- 開発者は既存データベースの確認、ストレージ使用量のデバッグ、アプリケーション状態の管理などに利用できる
- 通常のプライバシー期待のもとでは、この API の返却順序自体が識別情報を含むべきではない
- 中立的または正規化された形でのデータベースメタデータ表現が必要
- 実際の問題は、Firefox ベースブラウザで返却順序がまったく中立的ではなかった点にある
indexedDB.databases() が安定した識別子になった仕組み
- Firefox のプライベートブラウジングでは、
indexedDB.databases()はデータベースの作成順ではなく、内部ストレージ構造から導出された順序でメタデータを返す- 関連実装箇所は
dom/indexedDB/ActorsParent.cpp
- 関連実装箇所は
- プライベートブラウジングでは、データベース名をディスク識別子として直接使わない
- 代わりに、グローバルハッシュテーブル
StorageDatabaseNameHashtable = nsTHashMap<nsString, nsString>を通じて、UUID ベースのファイル名ベースへマッピングする - このマッピングは
OpenDatabaseOp::DoDatabaseWork()内のGetDatabaseFilenameBase()で行われる
- 代わりに、グローバルハッシュテーブル
aIsPrivateが true のとき、サイトが与えたデータベース名は生成された UUID に置き換えられ、グローバルなStorageDatabaseNameHashtableに保存される- キーにはデータベース名文字列だけが使われる
- IndexedDB のQuotaClient の寿命中維持される
- すべての origin 間で共有される
- Firefox を完全に再起動したときにのみ初期化される
- その後
indexedDB.databases()を呼ぶと、Firefox はGetDatabasesOp::DoDatabaseWork()でQuotaClient::GetDatabaseFilenames(...)を通じてデータベースファイル名を収集する- データベースのベース名は
nsTHashSetに挿入される - 反復前にいかなるソートも行われない
- データベースのベース名は
- 最終的な結果順序は、ハッシュセット内部のバケット配置を走査した結果で決まる
- UUID マッピングが Firefox プロセス寿命のあいだ安定して維持されるため、返却順序も生成された UUID 値、ハッシュ関数の挙動、ハッシュテーブル容量、挿入履歴の決定的な関数として維持される
- この順序はタブやプライベートウィンドウをまたいで維持され、Firefox 全体を再起動したときにのみリセットされる
- UUID マッピングとハッシュセット走査はいずれも origin スコープではなくプロセススコープである
再現方法
- 単純な PoC だけで挙動を実証できる
- 異なる 2 つの origin が同一のスクリプトをホストする
- 各スクリプトは、固定された名前集合のデータベースを作成し、
indexedDB.databases()を呼び、返却順序を抽出して出力する
- 影響を受ける Firefox のプライベートブラウジングおよび Tor Browser ビルドでは、2 つの origin が同じブラウザプロセス寿命のあいだ同一の順列を観測する
- ブラウザを再起動すると順列は変化する
- 概念的な出力例
- 作成順序:
a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p - 返却順序:
g,c,p,a,l,f,n,d,j,b,o,h,e,m,i,k
- 作成順序:
- 重要なのは正確な順序そのものではない
- 元の作成順序と異なる
- 無関係な origin 間で同じ順序が現れる
- リロード、新しいプライベートウィンドウ、すべてのプライベートウィンドウを閉じたあとでも維持される
- 新しい順序が生成されるのはブラウザ全体の再起動時のみ
- プライバシーの観点で望ましくない性質を直接確認できる
プライバシーへの影響
- この脆弱性により、1 つのブラウザランタイム内でcross-origin追跡とsame-origin追跡の両方が可能になる
-
cross-origin への影響
- 無関係な Web サイト同士が同じ識別子を独立に導出し、同一の実行中 Firefox または Tor Browser プロセスとやり取りしていると推測できる
- Cookie や他の共有ストレージがなくても、ドメイン間で活動を結び付けられる
-
same-origin への影響
- Firefox のプライベートブラウジングでは、Firefox プロセスが動作し続けている限り、すべてのプライベートウィンドウを閉じたあとでも識別子は維持される
- サイトは、見かけ上は新しいプライベートセッションに見える後続の訪問を再認識できる
- Tor Browser では、安定した識別子が実行中ブラウザプロセス内でNew Identity の隔離を事実上無効化する
- 完全に分離されるべきセッション間の結び付きを許してしまう
-
Tor Browser で特に深刻な理由
- Tor Browser は、クロスサイトでの結び付き可能性を減らし、ブラウザインスタンスレベルの識別を最小化するよう設計されている
- プロセス寿命のあいだ維持される安定した識別子は、この設計目標と正面から衝突する
- たとえプロセスの完全再起動までしか存続しなくても、活発な利用中の非連結性を弱めるには十分である
エントロピーとフィンガープリンティング容量
- このシグナルは安定しているだけでなく、容量も高い
- サイトが
N個のデータベース名を制御できる場合、観測可能な順列数はN! - 理論上のエントロピーは
log2(N!)
- サイトが
- 16 個の制御可能な名前があると、理論空間は約44 ビット
- 実環境で同時に存在するブラウザインスタンスを区別するのに十分な水準
- 内部ハッシュテーブルの挙動により、実際に到達可能な順列数はやや少ない可能性がある
- しかしセキュリティ上の本質は変わらない
- 露出した順序は依然として強い識別子として機能するのに十分なエントロピーを提供する
修正方法
- 正しい修正は、内部ストレージレイアウトに由来するエントロピー露出を止めること
- 最もクリーンな緩和策は、辞書順ソートのような正規の順序で結果を返すこと
- 開発者にとっての API の有用性を保ちながら、フィンガープリンティングシグナルを除去できる
- 呼び出しごとに出力をランダム化する方法でも、安定した順序を隠せる
- ただしソートのほうが単純で予測可能であり、開発者にも理解しやすい選択肢である
- セキュリティエンジニアリングの観点から見た理想的な修正条件
- 概念的複雑さが低い
- 互換性リスクが最小
- プライバシー漏えいを直接取り除く
責任ある開示
- Mozilla と Tor Project に対して責任ある開示が行われた
- Mozilla は Firefox 150 と ESR 140.10.0 で修正を配布した
- パッチは Mozilla Bug 2024220 で追跡されている
- 挙動の根源は Gecko の IndexedDB 実装にある
- Tor Browser を含む Gecko ベースの派生ブラウザも、独自の緩和策がなければ影響を受ける
プライバシー重視設計
- 小さな実装の詳細でも、意味のあるプライバシー問題につながりうる
- 無関係な Web サイトが、同じブラウザランタイムのあいだ origin を超えて活動を結び付けられる
- 識別子がユーザーの期待より長く残り、プライベートセッション境界を弱める
- 良い点は、修正が単純で効果的だということ
- 返却前に出力を正規化すれば、このエントロピー源を除去できる
- 期待されるプライバシー境界を回復できる
- 見落としやすいが影響が大きく、プライバシーに敏感な機能を作る際に注意を払う価値の高いタイプの脆弱性
1件のコメント
Hacker Newsのコメント
この研究は本当に印象的で、文章もとてもよく書けていると感じた。
最後に製品広告が出てくるのかと思ったが、なかったのでむしろ驚いた。
ただ、この会社の製品がフィンガープリンティングをしているなら、なぜこの脆弱性をMozillaに報告したのかは気になる。
非倫理的だとしても、競合との差別化のためには非公開のまま抱えていたほうがビジネス的には得ではないかと思う。
脅威アクターが責任ある開示で自分たちのzero-dayを潰すケースは、ほとんど見たことがない。
記事にある通り、この識別子はFirefoxプロセスが生きている間は維持されうるので、Tor Browserはセッションの終了時に必ず完全終了すべきだ。
1つのセッション内で異なる用途を混在させないことも重要だ。
OPが貼ったリンクは私のTor環境ではタイムアウトしたが、Wayback版は問題なく開けた。
それと、このテーマを扱っている学術研究者がいるのかも気になる。
EFFのような団体の活動は知っているが、NGOの活動家よりは大学教授やMSR、PARCのような純粋研究所のほうを探している。
プライバシーに強い関心がある立場としては、noscript、ublock origin、firefox containersという個人的なholy trinityでセキュリティはかなり確保できても、匿名性はフィンガープリンティングのせいで指の間からこぼれ落ちていくように感じる。
stylometryまで広くフィンガープリンティングに含めるなら、なおさらそうだと思う。
例としてPETSのような学会を調べると参考になる。
Webサイトがこうした情報に、ユーザーに尋ねたり知らせたりもせずアクセスできるという点自体が疑問だ。
なぜブラウザは、携帯電話のようにサーバーやアプリがこうした情報へアクセスするときに権限の許可を求める仕組みにしないのだろうか。
ブラウザのバージョンを知らせるuser agentもそれなりに合理的だし、システムにどのフォントがあるかを問う機能も、フォント対応の面から完全に消すのは難しい。
タイムゾーン、言語、キーボードレイアウト、画面サイズ、ウィンドウサイズも正常なWeb動作には必要だ。
動画や音声プレーヤーがどの形式をサポートしているかを知って、適切なメディアを返せるようにするのも当然だ。
JavaScriptが時刻を読める以上、サーバー時刻と比較してシステム時計のずれを割り出すのも簡単だ。
こうしたものが1つずつ積み重なると、ほぼすべてのブラウザが最終的には固有に識別されてしまう。
しかもその会社は最大の競合の資金もかなりの部分を支えている。
だから、この現実はそれほど驚くことではないと思う。
アプリは識別子やデバイス特性に、はるかに多くアクセスできる。
Google Play servicesがない比較的よく保護されたシステムでもそうだと思う。
むしろAppleの側は細かな制御がほとんどなくて残念に感じる。
ブラウザはすでにOSに匹敵する複雑さを持っているのだから、システムのどの部分でも意図せず露出し、悪用されうる。
記事に出てきたprocess-scopedという表現が少しわかりにくかった。
Mozillaは2021年にFirefox向けの実験的なone-process-per-siteを公開した際、デスクトップ版Firefoxではすべてのサイト間にOSプロセスレベルの境界を作ると説明していた記憶がある。
関連記事はIntroducing Site Isolation in Firefoxだ。
なので、この機能がまだ完全には展開されていないのか、それとも展開はされたがIndexedDBがその隔離の外にあるのかが気になる。
だとすると、かなり興味深い説明だと思う。
この説明だと、ブラウザを再起動すれば維持されないようにも聞こえるので、だとすれば攻撃者にとっての有用性はかなり下がるのではないかと思う。
Firefox Private Browsingでは、すべてのプライベートウィンドウを閉じてもFirefoxプロセスが生きていれば識別子は維持されうる。
Tor Browserでは、Cookieと閲覧履歴を消去し、新しいTor回線を使う完全リセットを意図したNew Identityを使っても、安定した識別子が維持されると理解している。
まずWebサイトがブラウザをフィンガープリントし、CookieにIDとフィンガープリントを保存する。
次のセッションで再度フィンガープリントしてCookieと照合し、値が変わっていれば古いフィンガープリントと新しいフィンガープリントをサーバーに一緒に知らせて結び付ける方式だ。
国家機関はすでに多くのノードを把握あるいは追跡できている可能性があり、複数のメタ情報を相互に関連付ければかなり正確に個人を識別できると思う。
常に100パーセント正確である必要はなく、周辺地域情報や壁越しに得る情報のように、対象そのもの以外の間接情報を大量に集めるだけでも十分だという考え方もある。
私には、これは一種のproxy情報で識別するやり方のように感じられる。
正直に言って、Web Standardsのかなりの部分は、実際の機能よりもフィンガープリンティングに多く使われているように見える。
IndexedDBも実際の保存用途で使っているサイトは少ない気がするし、それを本当に必要としているのは誰なのかと思う。
だから、Web標準を拡張し続ける方向性自体が間違っていると思う。
ブラウザはデバイスとやり取りする最小限のAPIだけを提供し、IndexedDBのような機能は、価値あるデータを漏らさないWebAssemblyライブラリとして実装することもできるのではないかと思う。
たとえばcanvasがプラットフォームごとのライブラリを呼ぶ描画ルーチンなしで、単に描画バッファへのアクセスだけを提供していたなら、フィンガープリンティング価値は大きく下がっていただろうと思う。
私がこれまで見た例では、gmailのようなサイトで長寿命の画像をキャッシュしたり、Cookieの代わりにログイン状態を維持する別の方法として使われていた。
ここは少し混乱した。
IndexedDBのUUIDがすべてのoriginで共有されるなら、順序ではなくデータベースの内容そのものでブラウザを識別できるのではないかと思った。
あるページが
a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,pというデータベースを作成し、順序を問い合わせると、グローバルな名前-UUIDマッピングに従って、たとえばg,c,p,a,l,f,n,d,j,b,o,h,e,m,i,kのような結果を得られる。核心となる脆弱性は、Firefoxプロセスが生きている間は、どのWebサイトが同じ名前集合のデータベースを作っても、内容とは無関係にまったく同じ順序を見るという点だ。
そのため、これは時間をまたいで維持される安定的で高エントロピーな識別子、つまりfingerprintになる。
originをまたいで共有され、サイトデータを削除したあとでも、同じ名前で再作成しさえすれば順序を通じてフィンガープリントを再取得できる。
そうでなければIndexedDBはあまりにも簡単なevercookieになっていただろう。
各originには、そのoriginと結び付いたデータベースの部分集合だけが見えると理解している。
Tor Browserは今でもデフォルトでJavaScriptを許可しているのだろうかと気になった。
私の理解では、JavaScriptの実行を止めればこの脆弱性の影響も受けないように思える。
JSを無効にしているユーザーは多くないので、すぐにずっと小さな集団に入ることになり、その中で固有になりやすくなる。
もちろんJSがなければ細かな情報を収集する手段は減るが、そのぶん少ない情報でも区別されやすくなる。
しかもTor Browserは妙なことに
navigator.platformをまったくspoofしていないので、User-AgentがWindowsに偽装されていても、サイトは依然としてLinuxユーザーかどうかを見分けられる。