Firefox/Tor 的 IndexedDB 指纹漏洞:一个实现细节如何破坏隐私隔离
今天 HN 上另一条让人心里发紧的,是这篇《We Found a Stable Firefox Identifier Linking All Your Private Tor Identities》。它最刺眼的地方,是揭示了一个很根本的问题:隐私漏洞不一定来自"直接访问敏感数据",而是来自"实现细节的确定性暴露"。
文章描述的问题是:Firefox 和 Tor Browser 的 indexedDB.databases() API 返回的数据库列表顺序是确定性的。这个顺序来自内部哈希表的迭代顺序,而哈希表的布局在浏览器进程生命周期内是稳定的。这意味着:任何网站都可以调用这个 API,观察返回的顺序,然后把这个顺序当作一个跨站标识符。
嗯,这个漏洞最让人不安的地方,是它破坏了 Tor Browser 的核心设计目标。
Tor Browser 的"新身份"(New Identity)功能,目的是让用户能够"重新开始":清除 cookie、清除历史、使用新的 Tor 电路。用户期望的是:之后的浏览活动无法和之前的活动关联。但这个漏洞让这种关联变得可能:即使你点击了"新身份",只要浏览器进程还在运行,indexedDB.databases() 返回的顺序就不会变。
文章里最有价值的部分,是它对"为什么这个顺序是稳定的"的技术分析。
在 Firefox 的隐私浏览模式下,数据库名称不会被直接用作文件名。而是通过一个全局哈希表映射到 UUID 生成的文件名。这个哈希表:
- 只在数据库名称字符串上建立映射
- 在 IndexedDB QuotaClient 生命周期内持久存在
- 在所有 origin 之间共享
- 只在 Firefox 完全重启时清除
然后当 indexedDB.databases() 被调用时,Firefox 收集数据库文件名,插入到一个哈希集合中,然后直接迭代这个集合返回结果。没有排序。所以返回的顺序由哈希表的内部布局决定,而这个布局在进程生命周期内是稳定的。
从隐私角度看,这个漏洞的影响很直接:
跨站影响:不相关的网站可以独立推导同一个标识符,推断它们在和同一个浏览器进程交互。这让它们能够在没有 cookie 或共享存储的情况下关联跨站活动。
同站影响:在 Firefox 隐私浏览模式下,标识符可以在所有隐私窗口关闭后仍然持久,只要 Firefox 进程还在运行。这意味着一个网站可以识别"看起来是全新隐私会话"的后续访问。在 Tor Browser 中,稳定的标识符实际上破坏了"新身份"的隔离。
熵和指纹容量:如果一个网站控制 N 个数据库名称,那么可观察的排列数是 N!,理论熵是 log2(N!)。用 16 个控制名称,理论空间大约 44 位。这远远足够区分现实中并发浏览器实例的数量。
从工程角度看,这个漏洞最有价值的启示,其实是"隐私 bug 不一定来自直接访问敏感数据"。
这个漏洞的根源是:indexedDB.databases() 这个 API 本身是合法的、有明确用途的(开发者可以用它来检查现有数据库、调试存储使用、管理应用状态)。但实现时暴露了内部存储布局的确定性顺序,而这个顺序恰好可以被用作标识符。
这意味着:即使 API 的设计本身没有隐私问题,实现细节也可能引入隐私问题。而这类问题很难通过"审计 API 设计"来发现,因为它们来自实现层面的偶然性。
文章里提到的修复方案很简单:在返回结果之前进行规范化排序(比如按字典序)。这保留了 API 对开发者的有用性,同时移除了指纹信号。
从更大的角度看,我觉得这篇文章真正提醒我们的,是另一件更根本的事:当浏览器实现开始暴露内部状态时,这些状态就不再是"实现细节",而是"可以被利用的标识符"。而这件事,目前几乎完全在开发者视野之外。
所以今天真正值得记住的,不是"IndexedDB 有漏洞",而是另一句更关键的话:隐私 bug 不一定来自直接访问敏感数据,而是来自确定性暴露内部实现细节。而这类问题,需要的是"在返回任何内部状态之前先规范化"的防御性思维。