Mullvad 退出 IP 出乎意料地具有识别性

Hacker News Top 论文

摘要

一项分析显示,Mullvad VPN 并非随机分配退出 IP,而是基于 WireGuard 密钥确定性分配,导致只有少量 IP 组合,能在不同会话中唯一识别用户,从而削弱了匿名性。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/05/15 03:28

# Mullvad 出口 IP 作为指纹向量 来源:https://tmctmt.com/posts/mullvad-exit-ips-as-a-fingerprinting-vector/ Mullvad 是少数为每个服务器提供多个出口 IP 的 VPN 提供商之一。如果两个人连接到同一台服务器,他们通常会被分配到不同的公网 IP。 仅有 578 台服务器(相比之下,Proton VPN 有 20,000 台 (https://protonvpn.com/vpn-servers)),这种垂直扩展方式合情合理,可以避免过多用户挤在一个 IP 上——否则在那些 IP 封禁和限流过于激进网站上会出问题。 令人惊讶的是,你每次连接服务器时所获得的出口 IP 并非随机分配,而是基于你的 WireGuard 密钥 (https://github.com/mullvad/mullvadvpn-app/issues/3777#issuecomment-1196825360) 确定性选择的,该密钥每 1 到 30 天轮换一次 (https://github.com/mullvad/mullvadvpn-app/blob/dd150ac3906fb8eccc60e2fabf9ee035636d1aa9/mullvad-types/src/wireguard.rs#L9)(除非你使用第三方客户端,这种情况下密钥永远不会轮换)。 但等等……如果每个服务器都为你分配一个独立选择的静态出口 IP,那么仅仅几个 IP 组合不就足以在所有 Mullvad 用户中唯一标识你了吗? ## 实际测试 我编写了一个脚本,反复更换我的公钥,并获取一组 9 台服务器的出口 IP。让它运行一夜后,收集到了 3650 个公钥的数据点,足以绘制出每台服务器的出口 IP 范围: | 主机名 | 起始 IP | 结束 IP | IP 数量 | |--------|--------|--------|--------| | au-syd-wg-101 | 103.136.147.5 | 103.136.147.64 | 60 | | cl-scl-wg-001 | 149.88.104.4 | 149.88.104.14 | 11 | | de-ber-wg-007 | 193.32.248.245 | 193.32.248.252 | 8 | | dk-cph-wg-002 | 45.129.56.196 | 45.129.56.226 | 31 | | fi-hel-wg-201 | 185.65.133.10 | 185.65.133.75 | 66 | | us-lax-wg-001 | 23.234.72.36 | 23.234.72.126 | 91 | | us-nyc-wg-602 | 146.70.168.132 | 146.70.168.190 | 59 | | us-sjc-wg-302 | 142.147.89.212 | 142.147.89.224 | 13 | | za-jnb-wg-002 | 154.47.30.145 | 154.47.30.155 | 11 | 这些池的大小加起来超过 8.2 万亿种出口 IP 组合,所以你可能会认为每个公钥都会被分配一组唯一的 IP 组合,因为碰撞概率极低。然而,我测试的所有公钥竟然只被分配了 284 种组合 (https://tmctmt.com/posts/mullvad-exit-ips-as-a-fingerprinting-vector/pubkeys.txt) 中的一种。 这是怎么回事? ## 不同 IP,相同比例 你可以通过计算出口 IP 到其池起始 IP 的距离,来得出该 IP 的数字位置。 例如,`au-syd-wg-101` 分配的 IP `103.136.147.53` 的基于 1 的索引是 49(X.X.X.53 - X.X.X.5 + 1)。 现在,如果你取上面链接中 284 种组合里任一组合的 IP 位置,并将其除以池大小,就会出现一个共同的比率: | 服务器 | IP | 位置 | 池大小 | 比率 | |--------|----|------|--------|------| | au-syd-wg-101 | 103.136.147.53 | 49 | 60 | 0.816 | | cl-scl-wg-001 | 149.88.104.12 | 9 | 11 | 0.818 | | de-ber-wg-007 | 193.32.248.251 | 7 | 8 | 0.875 | | dk-cph-wg-002 | 45.129.56.220 | 25 | 31 | 0.806 | | fi-hel-wg-201 | 185.65.133.63 | 54 | 66 | 0.818 | | us-lax-wg-001 | 23.234.72.109 | 74 | 91 | 0.813 | | us-nyc-wg-602 | 146.70.168.179 | 48 | 59 | 0.813 | | us-sjc-wg-302 | 142.147.89.222 | 11 | 13 | 0.846 | | za-jnb-wg-002 | 154.47.30.153 | 9 | 11 | 0.818 | 每个 IP 都落在其池的相同百分位中,在本例中是第 81 百分位。 这解释了组合数量有限的原因:Mullvad 只会在其所有服务器上分配相邻的出口 IP。但为什么要这样做呢? ## 特性还是漏洞? 值得注意的是,在所有观测到的 284 种 IP 组合中,服务器 `cl-scl-wg-001` 和 `za-jnb-wg-002` 始终共享彼此的 IP 索引。 它们的共同点是池大小均为 11,这给了我们一些线索。 在任何语言中,如果你用静态种子初始化一个随机数生成器(RNG),那么调用相同边界的 `rand-between` 函数始终会得到相同的结果: ```rust use rand::{Rng, SeedableRng}; use rand::rngs::StdRng; fn main() { let seed = 1234; for _ in 1..100 { let mut rng = StdRng::seed_from_u64(seed); let number = rng.random_range(0..1000); println!("{}", number) // 始终输出 56 } } ``` 因此,这两台服务器之间的共享索引表明,Mullvad 很可能使用了某种基于种子的 RNG 来选择出口 IP 索引,其中上限参数是池大小。 这相当直接,但当边界改变时会发生什么呢? ```rust use rand::{Rng, SeedableRng}; use rand::rngs::StdRng; fn main() { let seed = 12345; for bound in 10..100 { let mut rng = StdRng::seed_from_u64(seed); let number = rng.random_range(0..bound); let ratio = number as f64 / bound as f64; println!("{} {:.3} ", number, ratio) } } ``` ``` 5 0.500 5 0.455 6 0.500 6 0.462 7 0.500 7 0.467 8 0.500 9 0.529 9 0.500 10 0.526 10 0.500 11 0.524 11 0.500 12 0.522 12 0.500 13 0.520 13 0.500 14 0.519 14 0.500 15 0.517 ... ``` 事实证明,RNG 的熵池不受你提供边界的影响,至少在 Rust 中,每次第一次调用时都会生成相同的浮点数,并用作边界范围的乘数比例,例如:`min + round((max - min) * float)`(这可能是一个极大的简化)。 这与我们在 Mullvad 的出口 IP 选择算法中观察到的行为一致,所以可以放心地说这就是原因所在。 考虑到客户端 (https://github.com/mullvad/mullvadvpn-app) 也是用 Rust 编写的,以 Rust 作为后端语言也说得通。 问题是,我的程序员朋友中几乎没人能准确描述第二个代码片段中 `random_range` 会输出什么,而实际行为也让我大吃一惊。人们很自然地认为,每次增加边界都会扰乱熵,从而得到不同的数字,但实际情况并非如此。 有没有可能 Mullvad 的开发者也抱有这种常见误解,但实际上本意是提供无限数量的出口 IP 组合?我不确定,但这想法挺有趣的。 ## 关联身份 我制作了一个工具,可以推断出给定 IP 组合的最小和最大浮点值,地址为 https://tmctmt.github.io/mullvad-seed-estimator/。 截图中这组特定的 IP 解析出的浮点值范围在 0.2909 到 0.2943 之间,差值仅为 0.0034,这意味着 0.34% 的 Mullvad 用户共享这些 IP。粗略估计 Mullvad 有 10 万活跃用户,这相当于 340 个用户。 这肯定没有我最初想象的那么独特,但同时,超过 99% 的准确率也真的不算差? 举个例子,假设你是一个论坛版主,怀疑一个新面孔实际上是前一天被你封禁用户的马甲。你查看 IP 日志,尽管两个账户使用了不同的 Mullvad 服务器,但它们的 IP 对应浮点范围分别是 `0.4334 - 0.4428` 和 `0.4358 - 0.4423`,重叠部分很大。这让你有超过 99% 的把握认定他们是同一个人。 现在,将这种方法应用于通过数据泄露和法律渠道获得的 IP 日志,你就会明白如何通过类似的相关性攻击在 VPN 背后被去匿名化。 ## 保护自己 - 避免在一个公钥下多次切换服务器 - 通过退出 Mullvad 应用来强制轮换公钥

相似文章

推出VPN服务器出口IP指纹缓解措施

Hacker News Top

Mullvad VPN 正在推出一种新的缓解措施,以防止VPN服务器之间的出口IP指纹识别,该措施已应用于多个地点的多台服务器。