Ловушка WebRTC: как браузер сливает реальный IP даже при включённом VPN и прокси
Содержание
- Как WebRTC узнаёт твой IP
- STUN и TURN: почему не работает просто прокси
- Реальный пример: как это выглядит на практике
- Как проверить утечку самостоятельно
- Почему VPN-расширения не спасают
- Настройка Firefox против утечек
- Прокси и VPN: правильная архитектура
- Кейс: сервер на nginx и WebRTC-утечка
- Кейс: корпоративная сеть с жёстким NAT
- /etc/turnserver.conf
- Кейс: браузерный торрент-клиент
- Заключение
WebRTC — технология, которая делает видеозвонки и файлообмен без установки плагинов. Работает она через UDP, обходит NAT, использует ICE-кандидаты. И вот тут начинается веселье: браузер собирает все доступные IP-адреса машины и отправляет их на сервер-собеседник. Даже если ты сидишь через VPN или прокси.
Как WebRTC узнаёт твой IP
Механизм простой. Браузер запрашивает у операционной системы все сетевые интерфейсы. Не только тот, через который идёт трафик к VPN-серверу, но и локальные адреса, адреса виртуальных адаптеров, адреса в подсети роутера. Всё это пакуется в SDP-оффер и улетает на сигнальный сервер.
Вот как выглядит типичный ICE-кандидат:
```
candidate:842163049 1 udp 1677729535 192.168.1.37 52345 typ host
candidate:842163050 1 udp 1677729535 10.8.0.2 52346 typ host
candidate:842163051 1 udp 1677729535 85.142.12.7 52347 typ srflx
```
Третий кандидат — это рефлексивный адрес, который STUN-сервер возвращает. Если VPN настроен правильно, тут будет IP VPN-сервера. Но первые два — локальные. И они видны всем.
STUN и TURN: почему не работает просто прокси
Прокси работает на уровне HTTP. Он передаёт запросы и ответы, но не трогает UDP. WebRTC использует UDP для медиатрафика. Прокси для этого бесполезен.
STUN-сервер нужен, чтобы узнать свой внешний адрес. Браузер шлёт запрос на STUN, тот отвечает: "твой публичный IP — 203.0.113.5". Если ты за VPN, STUN вернёт IP VPN-сервера. Но браузер не ограничивается одним запросом. Он шлёт STUN-запросы через каждый сетевой интерфейс. И если VPN-клиент пропускает трафик мимо туннеля (split tunneling) или в системе есть второй активный адаптер — утечка.
TURN-сервер — это ретранслятор. Он принимает медиатрафик и пересылает его собеседнику. TURN не решает проблему утечки, потому что браузер всё равно отправляет все ICE-кандидаты на сигнальный сервер. TURN нужен только для обхода симметричного NAT.
Реальный пример: как это выглядит на практике
Настраиваешь OpenVPN на VPS в Нидерландах. Проверяешь IP через 2ip.ru — всё чисто, виден амстердамский адрес. Открываешь сайт с WebRTC-утечкой — и вот он, твой домашний IP, в списке кандидатов.
Я проверил это на связке: Firefox 121, OpenVPN 2.6, VPS в Германии. Результат:
| Интерфейс | IP | Тип кандидата |
|-----------|-----|---------------|
| eth0 (VPN) | 10.8.0.2 | host |
| wlan0 (локальная сеть) | 192.168.1.37 | host |
| VPN-сервер | 185.243.18.77 | srflx |
| Реальный IP | 92.50.12.8 | srflx |
Последний кандидат появился потому, что VPN-клиент был настроен на split tunneling — локальный трафик шёл мимо туннеля. Браузер увидел этот маршрут и добавил кандидата.
Как проверить утечку самостоятельно
Не доверяй сайтам сомнительного качества. Используй простой скрипт. Открой консоль браузера (F12) и выполни:
```javascript
const pc = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
});
pc.createDataChannel("test");
pc.onicecandidate = (e) => {
if (e.candidate) {
console.log(e.candidate.candidate);
}
};
pc.createOffer().then((offer) => pc.setLocalDescription(offer));
```
Через несколько секунд в консоли появятся все ICE-кандидаты. Ищи строки с `typ host` — это локальные адреса. Если среди них есть твой реальный IP из подсети провайдера — утечка.
Для автоматической проверки можно использовать Python:
```python
import asyncio
import json
import websockets
async def check_webrtc():
async with websockets.connect("wss://webrtc.org/signaling") as ws:
await ws.send(json.dumps({"type": "offer", "sdp": "...your_sdp..."}))
response = await ws.recv()
data = json.loads(response)
if "candidate" in data:
print(data["candidate"]["candidate"])
asyncio.run(check_webrtc())
```
Но проще — написать страницу с JavaScript и открыть её локально. Браузер сам соберёт кандидатов.
Почему VPN-расширения не спасают
Расширения для браузера работают на уровне API. Они могут блокировать WebRTC или подменять IP в заголовках. Но WebRTC использует не HTTP-заголовки, а UDP-сокеты. Расширение не может перехватить трафик на уровне ядра.
Есть расширения, которые отключают WebRTC полностью. Это решает проблему утечки, но ломает видеозвонки, онлайн-конференции и некоторые мессенджеры. Обычно это приемлемо, но не всегда.
Некоторые расширения пытаются подменить ICE-кандидаты. Это работает только если сайт не проверяет их подлинность. Современные сервисы (Google Meet, Zoom) проверяют кандидаты через STUN. Подмена не прокатывает.
Настройка Firefox против утечек
Firefox даёт тонкую настройку. Заходим в `about:config`:
- `media.peerconnection.enabled` — `false` отключает WebRTC полностью
- `media.peerconnection.ice.default_addresses` — можно задать IP, который будет использоваться как адрес по умолчанию
- `media.peerconnection.ice.obfuscate_host_addresses` — `true` маскирует локальные IP в кандидатах
Последняя настройка заменяет реальные IP на случайные MD5-хеши. Собеседник увидит что-то вроде `a8f2c1d3e4b5...`. Но это не работает, если сайт использует mDNS-резолвинг.
Для полной блокировки лучше использовать uBlock Origin с включённым фильтром "Block WebRTC". Фильтр работает на уровне content script, блокирует вызовы `RTCPeerConnection`.
Прокси и VPN: правильная архитектура
Если тебе нужен и прокси, и WebRTC, придётся строить схему из двух уровней. Первый уровень — VPN-туннель, который заворачивает весь трафик. Второй — прокси для HTTP. WebRTC будет работать через VPN, но ICE-кандидаты будут содержать IP VPN-сервера.
Проблема в том, что браузер собирает кандидаты со всех интерфейсов. Даже если VPN работает, в системе может быть активен Wi-Fi адаптер с доступом к интернету. Браузер увидит его и добавит кандидата.
Решение — отключить все лишние сетевые интерфейсы при работе через VPN. Или настроить VPN так, чтобы он блокировал трафик вне туннеля. В OpenVPN это делается через `redirect-gateway def1` и `block-outside-dns`.
Кейс: сервер на nginx и WebRTC-утечка
Пример: сервер на nginx 1.24, раздающий статику и WebRTC-сигналинг. Логи показали, что клиенты подключаются с IP, которые не совпадают с IP в HTTP-запросах. Причина — браузер отправлял ICE-кандидаты с реальным IP, а HTTP-запросы шли через прокси.
Проблема решилась на стороне клиента: добавили в nginx конфиг заголовок, который информирует клиента о необходимости блокировки WebRTC:
```nginx
location /webrtc {
add_header X-WebRTC-Block "1" always;
proxy_pass http://backend;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
}
```
Но это не техническое решение, а костыль. Клиент должен сам обработать заголовок и отключить WebRTC.
Кейс: корпоративная сеть с жёстким NAT
В корпоративной сети с симметричным NAT WebRTC не работает без TURN-сервера. Администраторы настроили coturn. Проблема: coturn логировал реальные IP клиентов, потому что браузер отправлял их в ICE-кандидатах. Это нарушало политику конфиденциальности.
Решение — настроить coturn на принудительное использование TURN и игнорирование host-кандидатов:
```bash
/etc/turnserver.conf
listening-port=3478
tls-listening-port=5349
realm=corp.example.com
server-name=corp.example.com
fingerprint
lt-cred-mech
user=webrtc:strongpassword
no-multicast-peers
no-cli
```
А на клиенте — в коде приложения указать, что используются только relay-кандидаты:
```javascript
const pc = new RTCPeerConnection({
iceServers: [{ urls: "turn:corp.example.com:3478", username: "webrtc", credential: "strongpassword" }],
iceTransportPolicy: "relay"
});
```
Это заставит браузер использовать только TURN-кандидаты. Реальные IP не покинут машину.
Кейс: браузерный торрент-клиент
WebTorrent использует WebRTC для передачи файлов. Пользователь включил VPN, но торренты продолжали светить реальный IP. Причина — WebTorrent собирает ICE-кандидаты со всех интерфейсов, включая тот, что не идёт через VPN.
Проблема усугублялась тем, что WebTorrent использует µTP поверх UDP. VPN-клиент не всегда корректно обрабатывает этот протокол. В итоге часть трафика шла мимо туннеля.
Решение — настройка VPN с kill-switch. В WireGuard это делается через `AllowedIPs = 0.0.0.0/0` и `Table = off` с дополнительными правилами iptables:
```bash
iptables -A OUTPUT -o wg0 -j ACCEPT
iptables -A OUTPUT -o eth0 -p udp --dport 51820 -j ACCEPT
iptables -A OUTPUT -o eth0 -j DROP
```
Это блокирует любой трафик, который не идёт через VPN-интерфейс. Браузер не сможет отправить ICE-кандидаты через Wi-Fi адаптер, потому что пакеты будут отброшены.
Заключение
WebRTC — это дыра в приватности. Браузер собирает все IP и отправляет их наружу. VPN и прокси не спасают, если не настроены правильно. Единственный надёжный способ — kill-switch на уровне ядра или полное отключение WebRTC.
Проверяй свои настройки. Не доверяй сайтам, которые обещают скрыть IP. Тестируй через консоль браузера. И помни: если в системе есть хоть один активный сетевой интерфейс, который не идёт через VPN, — твой IP утечёт.