跳转到内容

Mullvad 给了你 8 万亿种出口 IP 组合。9 台服务器就把你找出来了。

作者:Ritabrata Maiti · · 约 8 分钟阅读

Play

Mullvad 是少数几家给每台服务器分配不同公网 IP 的 VPN 服务商之一。 全网 578 台服务器,朴素地算一下,跨网络的出口 IP 组合超过 8 万亿种。 他们的卖点是:你融入一个非常大的人群里。

一位以 tmctmt 为笔名的研究者写了个脚本,轮换了 3,650 个 WireGuard 密钥,观察每个密钥在 9 台服务器上分别被分到哪些出口 IP。他原本期望 看到成千上万种不同的组合,结果只看到 284 种。 完整研究在这里

原因不光鲜,影响不小。

当你连上 Mullvad 的某台服务器,分给你的出口 IP 不是随机的。它是 从每台服务器自己的 IP 池里确定性挑出来的,种子是你的 WireGuard 公钥。每台服务器的池子大小不一样。悉尼有 60 个 IP,赫尔辛基 66 个, 洛杉矶 91 个,圣地亚哥 11 个。如果你用 Mullvad 官方 app,密钥每 1 到 30 天轮换一次;如果你用自己的 WireGuard 客户端,永远不轮换。

到这里都还算合理。同一个密钥贴住同一个 IP,可以避免一个外网 IP 被 反复重连的用户砸;同时让按 IP 限速的网站对 Mullvad 用户在一个会话 内表现正常。

问题在挑 IP 的算法本身。

挑 IP 的算法用公钥作为种子初始化标准库的 RNG,然后抽出一个 0 到 1 之间的浮点数。把这个浮点数按当前服务器池子的大小缩放,得到索引。 这部分讲得通。破坏隐私的部分是:每台服务器抽出来的是同一个 浮点数,因为 RNG 是用同一个公钥做的种子,调用之间变的只是上界。

后果是你在不同服务器上拿到的出口 IP,落在各自池子的同一个百分位 里。如果你的浮点数刚好是 0.82,那么你在悉尼是 60 个里的第 49 个, 在圣地亚哥是 11 个里的第 9 个,在赫尔辛基是 66 个里的第 54 个。 IP 不一样,相对位置一样。

如果你曾经好奇为什么两台较小的 Mullvad 服务器似乎给你”同样的” 索引,原因就是这个。圣地亚哥和约翰内斯堡的池子大小都是 11。同样 的浮点数缩放到同样的大小,给你的是同样的位置。

这是文章的标题点。各个池子大小相乘是 8.2 万亿种组合,纸面上。 实际上浮点数只有一个,所以跨所有服务器的整个出口 IP 向量都被这 一个数参数化了。研究者采样的 3,650 个公钥在 9 台服务器上只产出 284 种不同组合,原因是被采样的空间不是十三维的。它是一维的, 然后投影出来。

284 这个数字是在 9 台服务器下这个投影的分辨率。服务器越多,分辨率 越高,因为每多一个池子大小,就把单位区间切得更细。服务器越少, 分辨率越低,但是降得不像你期望的那么多。

作者写了一个小工具:输入一组观察到的出口 IP,反解出能产生这组 IP 的浮点数区间。9 个选得好的出口 IP 把区间挤到 0.0034 宽。按估计的 10 万个 Mullvad 活跃用户算,那大约是 340 个人。

在人群里,340 人不少。在去匿名化攻击里,340 人不多。如果一个论坛 有 IP 日志,怀疑两个账号是同一个用户,两个账号都通过 Mullvad 在 不同服务器连接,那两组出口 IP 反解出来的浮点数区间的交集,给出 “这两个账号共享同一个公钥”的非常高概率。再叠上时间、user agent、 语言头,或者广告网络已经在收集的那种轻量浏览器指纹,嫌疑池就 缩到一个人。

有意思的攻击不是某个论坛的版主拿着自己的日志。有意思的是跨服务的 日志合并,这件事在诉讼、传票和被盗的数据泄露里早就在发生。Mullvad 按服务器的 IP 多样性,在两个不相关的服务把它们各自的出口 IP 记录 凑到同一份相关性分析里的那一刻,就不再是防线了。

Mullvad 的联合创始人在文章发表的当天上午就发了回应。短版本: 描述的部分行为是有意的,部分不是;非预期的部分先在一小部分集群 上打补丁,预期的部分也在内部重新评估。他还委婉地建议未来的研究者 在公开发现之前考虑先通知厂商。

这是一个小厂商在某个周二上午被吓到时能给出的接近最好的回应,也指 向这个故事里最值得停下来想的地方。

直接漏洞是一个用种子初始化的 RNG,每次调用的第一次抽取被当作一个 新的随机数来用,而实际上它每次都是同一个数。一次能说得过去的代码 review 是有可能漏掉这种问题的。Rust 文档没有把这个行为写在显眼的 位置,能抓到它的测试,也就是”我跨池子的出口 IP 百分位之间有相关性 吗”这种测试,不是任何人会去写的测试。

更深的漏洞是设计上的一个假设:按密钥的粘性和按服务器的独立性是 兼容的。它们不兼容,对任何用单个种子化浮点数推导索引的挑选算法 都不兼容。想要真正的独立,你要么每次调用都用按服务器的盐重新做 种子,要么在抽取之前先把公钥和服务器身份混合在一起,要么干脆放弃 确定性、接受运维成本。

这里有一个关于密码学确定性的清晰教训,我就不展开了,但对任何在 做隐私相关软件的人来说,有一个实用的教训值得明说:确定性的行为, 是既让用户体验可预测、又让用户跨观测可被关联的东西。让系统在不同 会话之间显得”连贯”的本能,和把一个连接 key 递到分析者手里的本能, 是同一个本能。

作者列了两个缓解手段。两个都只是降低指纹的价值,不能消除它。

第一是公钥不变的时候不要切换服务器。如果你永远只从一台 Mullvad 服务器连接,观察者只看得到一个出口 IP,只能锁定那个池子里其中一段 可能的浮点数区间,而不是多个池子区间的交集。一段单独的区间覆盖 的用户基数比交集大得多。

第二是强制轮换公钥,Mullvad 的 app 在你登出再登入时会做。新公钥 产生新的浮点数和新的出口 IP 向量。轮换前对你的观察仍然链接到旧 公钥下发生的那些活动。

两个都不令人满意。两个都只是补丁落地前用户能做的。

VPN 是当作”混入人群”的工具卖的。这个品类十年来都在比服务器数、 司法管辖、“我们不记日志”。这一切都帮不上忙,如果隧道本身发出的 信号能跨出口稳定关联起来。Mullvad 是这个品类里口碑较好的运营商 之一,做这个的人也是真在乎这件事的人,回应方式也匹配。如果这种 设计能在他们眼皮底下溜过去,那么有理由假设:相似的设计正在那些 不那么在乎的运营商那里安静地存在着。

对任何使用隐私工具的人来说,更健康的框架是:工具改变的是谁能看到 你这条边界。它不会让你对这条边界内部的人变得不可见。VPN 保护你 不被 ISP 看到,不被咖啡馆 Wi-Fi 看到。它不保护你不被你正在访问 的网站看到,网站仍然看得到你的浏览器、你的时序、还有你的隧道在 不同会话之间恰好泄漏的关于你身份的任何东西。假装不是这样,是 人们最后会被惊讶到的原因。

现代网络会话里很多泄漏面是在浏览器里,不在隧道里。Canvas 指纹、 字体枚举、音频上下文指纹、你以为已经屏蔽的第三方 cookie、把 标识符塞进 URL 的重定向链、剪贴板、你的标签页之间互相说话的 方式、你忘了自己装的某个扩展和它来源之间说话的方式。Mullvad 这个 故事有意思的地方在于,它是那种少见的情况:泄漏在浏览器之下发生, 浏览器自己看不到,要靠帮手才能看到。

我做的是 Browy,一个开源 AI 智能体,住在 Chrome 侧边栏和 DevTools REPL 里。它驱动你正看着的真实标签页。 你可以指着一个页面问它,用自然语言问:这个页面在发什么、它存了 什么、它从哪里加载什么、它在用什么尝试识别你。它会打开 Network 面板和 Application 面板替你读,然后在你提问的同一段对话里告诉你 它发现了什么。它不会把你的浏览发给任何地方。模型调用走出去、 答案返回来,其余的都发生在你的机器上。

Mullvad 这个 bug 是服务端的问题。Browy 看不到。但是更广的习惯, 看着你的浏览器在你使用它的时候到底在发什么,是过去十年里每一类 隐私泄漏都奖励的习惯。Mullvad 故事的 30 秒视频版本就在这篇文章 顶部。带种子估算工具的完整研究在 tmctmt 的站点