Mullvad exit IP는 놀라울 정도로 식별 가능함

1 hour ago 1
  • Mullvad는 서버 하나에 여러 exit IP를 두지만, WireGuard 키 기반으로 결정적으로 배정해 접속마다 무작위로 바뀌지 않음
  • 9개 서버에서 pubkey를 반복 변경해 모은 3,650개 데이터 포인트는 가능한 8.2조 개 조합 중 284개 조합에만 배정됨
  • 각 서버의 exit IP는 풀 안에서 비슷한 백분위 위치에 놓이며, 한 조합은 여러 서버에서 대체로 81번째 백분위에 맞춰짐
  • 원인은 pubkey나 터널 주소를 seed로 쓰고 풀 크기를 상한으로 넣는 seed 기반 RNG로 exit IP index를 고르는 구조로 보임
  • IP 로그의 float 범위가 겹치면 서로 다른 Mullvad 서버를 써도 계정 간 상관관계가 가능해져 익명성 위험이 커짐

Mullvad exit IP가 식별 벡터가 되는 구조

  • Mullvad는 서버 하나에 여러 exit IP를 제공하며, 같은 서버에 연결한 두 사용자가 보통 서로 다른 공개 IP를 받음
  • 서버 수는 578대로, Proton VPN의 20,000대보다 적어 한 IP에 사용자를 몰아넣지 않는 수직 확장이 과도한 IP 차단·속도 제한을 피하는 데 유리함
  • 하지만 서버에 연결할 때마다 exit IP가 무작위로 바뀌지 않고, WireGuard 키를 기반으로 결정적으로 선택
  • WireGuard 키는 1~30일마다 회전하지만, 서드파티 클라이언트에서는 회전하지 않음
  • 서버마다 독립적으로 고정 exit IP가 배정된다면, 몇 개 서버의 IP 조합만으로 다른 Mullvad 사용자와 구분될 수 있음

9개 서버에서 관찰한 exit IP 조합

  • pubkey를 반복 변경하며 9개 서버의 exit IP를 수집하는 스크립트를 밤새 실행해 3,650개 pubkey 데이터 포인트를 얻음
  • 각 서버의 exit IP 범위는 다음처럼 관찰됨
Hostname Start IP End IP # IPs
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조 개 이상의 exit IP 조합이 가능해 보임
  • 실제 테스트된 모든 pubkey는 그중 284개 조합 중 하나에만 배정됨
  • 가능한 조합 수에 비해 관찰된 조합 수가 매우 적어, 서버별 IP 선택이 독립적이지 않다는 단서가 됨

서로 다른 IP가 같은 백분위에 놓이는 패턴

  • exit IP는 풀 시작 IP에서 얼마나 떨어져 있는지로 숫자 위치를 계산할 수 있음
  • 예를 들어 au-syd-wg-101에서 103.136.147.53은 103.136.147.5부터 세면 1-based index가 49임
  • 관찰된 조합의 IP 위치를 각 서버의 풀 크기로 나누면, 서로 다른 서버에서도 거의 같은 비율이 나타남
Server IP Position Pool size Ratio
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는 모든 서버에서 서로 이웃한 위치의 exit IP만 배정하는 것처럼 보임

seed 기반 난수 선택으로 보이는 원인

  • cl-scl-wg-001과 za-jnb-wg-002는 관찰된 284개 IP 조합 전체에서 항상 같은 IP index를 공유함
  • 두 서버의 공통점은 풀 크기 11이며, 동일한 seed와 동일한 bounds를 가진 난수 호출이 같은 결과를 내는 구조와 맞음
  • 정적 seed로 RNG를 초기화한 뒤 같은 범위에서 난수를 뽑으면 같은 결과가 반복됨
  • Mullvad는 pubkey 또는 터널 주소를 seed로 쓰고, 풀 크기를 상한값으로 넣는 seed 기반 RNG로 exit IP index를 고르는 것으로 보임
  • bounds가 바뀌어도 RNG의 엔트로피 풀은 영향을 받지 않으며, Rust에서는 첫 호출에서 같은 float가 생성되어 bounds에 곱해지는 방식과 맞아떨어짐
  • 이 동작은 min + round((max - min) * float)처럼 단순화해 볼 수 있지만, 크게 단순화한 표현일 수 있음
  • 이런 특성 때문에 풀 크기가 달라도 같은 seed에서 나온 float가 각 서버 풀의 비슷한 비율 지점으로 매핑됨
  • Mullvad 클라이언트가 Rust로 작성되어 있어 Rust가 백엔드 언어일 가능성도 있지만, 이는 클라이언트 구현에 근거한 판단에 그침
  • random_range가 bounds 변화에 따라 어떻게 동작하는지 정확히 예상하기 어렵고, bounds 증가가 엔트로피와 섞여 다른 수를 만들 것이라고 생각하기 쉽지만 실제 관찰과는 다름

IP 로그 상관관계로 생기는 익명성 위험

  • 특정 IP 조합에서 가능한 float 값의 최솟값과 최댓값을 추정하는 도구가 mullvad-seed-estimator로 제공됨
  • 스크린샷의 특정 IP 집합은 0.2909~0.2943 사이의 float 값으로 해석되며, 차이는 0.0034임
  • 이는 Mullvad 사용자 중 0.34% 가 이 IP들을 공유한다는 뜻이고, 활성 사용자 100,000명이라는 대략적 추정에서는 340명에 해당함
  • 처음 예상한 수준만큼 고유하지는 않지만, 99% 이상 정확도는 낮지 않음
  • 예를 들어 포럼 관리자가 전날 차단한 사용자의 sockpuppet으로 의심되는 새 계정을 확인할 때, 두 계정이 서로 다른 Mullvad 서버를 써도 IP 로그가 0.4334 - 0.4428과 0.4358 - 0.4423처럼 겹치는 float 범위에 있으면 같은 사람일 확률이 99%를 넘음
  • 데이터 유출이나 법적 절차로 확보한 IP 로그에도 같은 방식의 상관관계를 적용하면, VPN 뒤의 사용자가 익명성을 잃을 수 있음

보호 방법

  • pubkey 하나당 서버를 한 번 이상 바꾸지 않는 것이 권장됨
  • Mullvad 앱에서 로그아웃해 pubkey를 강제 회전할 수 있음
Read Entire Article