Mullvad 给了你 8 万亿种出口 IP 组合。9 台服务器就把你找出来了。
作者:Ritabrata Maiti · · 约 8 分钟阅读
Mullvad 是少数几家给每台服务器分配不同公网 IP 的 VPN 服务商之一。 全网 578 台服务器,朴素地算一下,跨网络的出口 IP 组合超过 8 万亿种。 他们的卖点是:你融入一个非常大的人群里。
一位以 tmctmt 为笔名的研究者写了个脚本,轮换了 3,650 个 WireGuard 密钥,观察每个密钥在 9 台服务器上分别被分到哪些出口 IP。他原本期望 看到成千上万种不同的组合,结果只看到 284 种。 完整研究在这里。
原因不光鲜,影响不小。
选 IP 的逻辑是怎样的
Section titled “选 IP 的逻辑是怎样的”当你连上 Mullvad 的某台服务器,分给你的出口 IP 不是随机的。它是 从每台服务器自己的 IP 池里确定性挑出来的,种子是你的 WireGuard 公钥。每台服务器的池子大小不一样。悉尼有 60 个 IP,赫尔辛基 66 个, 洛杉矶 91 个,圣地亚哥 11 个。如果你用 Mullvad 官方 app,密钥每 1 到 30 天轮换一次;如果你用自己的 WireGuard 客户端,永远不轮换。
到这里都还算合理。同一个密钥贴住同一个 IP,可以避免一个外网 IP 被 反复重连的用户砸;同时让按 IP 限速的网站对 Mullvad 用户在一个会话 内表现正常。
问题在挑 IP 的算法本身。
一个浮点数,多个池子
Section titled “一个浮点数,多个池子”挑 IP 的算法用公钥作为种子初始化标准库的 RNG,然后抽出一个 0 到 1 之间的浮点数。把这个浮点数按当前服务器池子的大小缩放,得到索引。 这部分讲得通。破坏隐私的部分是:每台服务器抽出来的是同一个 浮点数,因为 RNG 是用同一个公钥做的种子,调用之间变的只是上界。
后果是你在不同服务器上拿到的出口 IP,落在各自池子的同一个百分位 里。如果你的浮点数刚好是 0.82,那么你在悉尼是 60 个里的第 49 个, 在圣地亚哥是 11 个里的第 9 个,在赫尔辛基是 66 个里的第 54 个。 IP 不一样,相对位置一样。
如果你曾经好奇为什么两台较小的 Mullvad 服务器似乎给你”同样的” 索引,原因就是这个。圣地亚哥和约翰内斯堡的池子大小都是 11。同样 的浮点数缩放到同样的大小,给你的是同样的位置。
为什么 8.2 万亿坍缩成 284
Section titled “为什么 8.2 万亿坍缩成 284”这是文章的标题点。各个池子大小相乘是 8.2 万亿种组合,纸面上。 实际上浮点数只有一个,所以跨所有服务器的整个出口 IP 向量都被这 一个数参数化了。研究者采样的 3,650 个公钥在 9 台服务器上只产出 284 种不同组合,原因是被采样的空间不是十三维的。它是一维的, 然后投影出来。
284 这个数字是在 9 台服务器下这个投影的分辨率。服务器越多,分辨率 越高,因为每多一个池子大小,就把单位区间切得更细。服务器越少, 分辨率越低,但是降得不像你期望的那么多。
对用户意味着什么
Section titled “对用户意味着什么”作者写了一个小工具:输入一组观察到的出口 IP,反解出能产生这组 IP 的浮点数区间。9 个选得好的出口 IP 把区间挤到 0.0034 宽。按估计的 10 万个 Mullvad 活跃用户算,那大约是 340 个人。
在人群里,340 人不少。在去匿名化攻击里,340 人不多。如果一个论坛 有 IP 日志,怀疑两个账号是同一个用户,两个账号都通过 Mullvad 在 不同服务器连接,那两组出口 IP 反解出来的浮点数区间的交集,给出 “这两个账号共享同一个公钥”的非常高概率。再叠上时间、user agent、 语言头,或者广告网络已经在收集的那种轻量浏览器指纹,嫌疑池就 缩到一个人。
有意思的攻击不是某个论坛的版主拿着自己的日志。有意思的是跨服务的 日志合并,这件事在诉讼、传票和被盗的数据泄露里早就在发生。Mullvad 按服务器的 IP 多样性,在两个不相关的服务把它们各自的出口 IP 记录 凑到同一份相关性分析里的那一刻,就不再是防线了。
Mullvad 的联合创始人在文章发表的当天上午就发了回应。短版本: 描述的部分行为是有意的,部分不是;非预期的部分先在一小部分集群 上打补丁,预期的部分也在内部重新评估。他还委婉地建议未来的研究者 在公开发现之前考虑先通知厂商。
这是一个小厂商在某个周二上午被吓到时能给出的接近最好的回应,也指 向这个故事里最值得停下来想的地方。
漏洞背后的漏洞
Section titled “漏洞背后的漏洞”直接漏洞是一个用种子初始化的 RNG,每次调用的第一次抽取被当作一个 新的随机数来用,而实际上它每次都是同一个数。一次能说得过去的代码 review 是有可能漏掉这种问题的。Rust 文档没有把这个行为写在显眼的 位置,能抓到它的测试,也就是”我跨池子的出口 IP 百分位之间有相关性 吗”这种测试,不是任何人会去写的测试。
更深的漏洞是设计上的一个假设:按密钥的粘性和按服务器的独立性是 兼容的。它们不兼容,对任何用单个种子化浮点数推导索引的挑选算法 都不兼容。想要真正的独立,你要么每次调用都用按服务器的盐重新做 种子,要么在抽取之前先把公钥和服务器身份混合在一起,要么干脆放弃 确定性、接受运维成本。
这里有一个关于密码学确定性的清晰教训,我就不展开了,但对任何在 做隐私相关软件的人来说,有一个实用的教训值得明说:确定性的行为, 是既让用户体验可预测、又让用户跨观测可被关联的东西。让系统在不同 会话之间显得”连贯”的本能,和把一个连接 key 递到分析者手里的本能, 是同一个本能。
用户今天能做什么
Section titled “用户今天能做什么”作者列了两个缓解手段。两个都只是降低指纹的价值,不能消除它。
第一是公钥不变的时候不要切换服务器。如果你永远只从一台 Mullvad 服务器连接,观察者只看得到一个出口 IP,只能锁定那个池子里其中一段 可能的浮点数区间,而不是多个池子区间的交集。一段单独的区间覆盖 的用户基数比交集大得多。
第二是强制轮换公钥,Mullvad 的 app 在你登出再登入时会做。新公钥 产生新的浮点数和新的出口 IP 向量。轮换前对你的观察仍然链接到旧 公钥下发生的那些活动。
两个都不令人满意。两个都只是补丁落地前用户能做的。
隐私工具的大局
Section titled “隐私工具的大局”VPN 是当作”混入人群”的工具卖的。这个品类十年来都在比服务器数、 司法管辖、“我们不记日志”。这一切都帮不上忙,如果隧道本身发出的 信号能跨出口稳定关联起来。Mullvad 是这个品类里口碑较好的运营商 之一,做这个的人也是真在乎这件事的人,回应方式也匹配。如果这种 设计能在他们眼皮底下溜过去,那么有理由假设:相似的设计正在那些 不那么在乎的运营商那里安静地存在着。
对任何使用隐私工具的人来说,更健康的框架是:工具改变的是谁能看到 你这条边界。它不会让你对这条边界内部的人变得不可见。VPN 保护你 不被 ISP 看到,不被咖啡馆 Wi-Fi 看到。它不保护你不被你正在访问 的网站看到,网站仍然看得到你的浏览器、你的时序、还有你的隧道在 不同会话之间恰好泄漏的关于你身份的任何东西。假装不是这样,是 人们最后会被惊讶到的原因。
这和浏览器有什么关系
Section titled “这和浏览器有什么关系”现代网络会话里很多泄漏面是在浏览器里,不在隧道里。Canvas 指纹、 字体枚举、音频上下文指纹、你以为已经屏蔽的第三方 cookie、把 标识符塞进 URL 的重定向链、剪贴板、你的标签页之间互相说话的 方式、你忘了自己装的某个扩展和它来源之间说话的方式。Mullvad 这个 故事有意思的地方在于,它是那种少见的情况:泄漏在浏览器之下发生, 浏览器自己看不到,要靠帮手才能看到。
我做的是 Browy,一个开源 AI 智能体,住在 Chrome 侧边栏和 DevTools REPL 里。它驱动你正看着的真实标签页。 你可以指着一个页面问它,用自然语言问:这个页面在发什么、它存了 什么、它从哪里加载什么、它在用什么尝试识别你。它会打开 Network 面板和 Application 面板替你读,然后在你提问的同一段对话里告诉你 它发现了什么。它不会把你的浏览发给任何地方。模型调用走出去、 答案返回来,其余的都发生在你的机器上。
Mullvad 这个 bug 是服务端的问题。Browy 看不到。但是更广的习惯, 看着你的浏览器在你使用它的时候到底在发什么,是过去十年里每一类 隐私泄漏都奖励的习惯。Mullvad 故事的 30 秒视频版本就在这篇文章 顶部。带种子估算工具的完整研究在 tmctmt 的站点。