Mullvad 退出 IP 出乎意料地具有识别性
摘要
一项分析显示,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指纹缓解措施
Mullvad VPN 正在推出一种新的缓解措施,以防止VPN服务器之间的出口IP指纹识别,该措施已应用于多个地点的多台服务器。
Mullvad 的 CEO 是瑞典厄勒布鲁党的主要资助者
注重隐私的 VPN 公司 Mullvad 的 CEO 被揭露为瑞典厄勒布鲁一个政党的主要财务支持者。
GDID Windows – 切断即使在VPN下仍追踪你的跟踪器
一篇文章解释Windows如何携带一个持久标识符(GDID),即使在VPN后方仍能追踪用户,以及如何禁用发送该标识符的服务。
警方声称黑入了犯罪分子“自以为安全”的VPN
经过多年调查,警方与国际机构摧毁了一款犯罪分子使用的VPN服务,查封了服务器并识别了用户。
我们发现了一个稳定的 Firefox 标识符,可关联你所有私密的 Tor 身份
Firefox IndexedDB 排序漏洞让任意站点生成稳定的跨源标识符,即使在 Tor 浏览器执行“新身份”重置后仍持续存在;Mozilla 已在 Firefox 150 与 ESR 140.10 中修复。