← Назад в базу знаний

Утечка реального IP через WebRTC при SOCKS5: как детектить и закрыть

SOCKS5-прокси в браузере — это не «настроил и забыл». WebRTC умеет обходить прокси так, что ты об этом узнаешь только когда уже поздно. Разбираем, как это работает, как проверить и как починить.

Почему SOCKS5 не спасает от WebRTC

SOCKS5 — транспортный уровень. Браузер отдаёт TCP-соединение прокси, тот его гонит дальше. Всё, что идёт через TCP — HTTP, HTTPS, WebSocket — попадает под прокси. Проблема в том, что WebRTC использует UDP. А UDP через SOCKS5 требует отдельного согласования (UDP ASSOCIATE), и большинство браузеров эту схему либо не поддерживают, либо делают криво.

Firefox и Chrome при настройке SOCKS5 обычно выставляют флаг «использовать прокси для DNS» и «для всех протоколов». Но WebRTC-стек живёт в отдельном процессе (в Chrome — в сетевом сервисе), и ICMP/UDP-пакеты для сбора ICE-кандидатов он шлёт напрямую через системный интерфейс. Прокси тут не при делах.

Итог: страница через прокси открылась, а STUN-запрос ушёл с твоего реального IP. Сервер видит его в SDP-ответе. Хорошая новость — это детектится. Плохая — детектить надо самому, никто за тебя не проверит.

Что именно утекает

WebRTC собирает ICE-кандидаты трёх типов: host (локальный IP машины в LAN), srflx (публичный IP, полученный через STUN), relay (адрес TURN-сервера). Для деанона интересны srflx и host.

Host-кандидат отдаёт внутренний адрес — 192.168.x.x, 10.x.x.x, IPv6 link-local. Это не публичный IP, но это фингерпринт: по подсети можно привязать к провайдеру и региону. Srflx — это уже прямой публичный адрес, который STUN-сервер увидел как источник пакета.

Формат кандидата в SDP:

```

a=candidate:1 1 UDP 2122260223 203.0.113.45 54321 typ srflx raddr 192.168.1.10 rport 54321

```

Здесь `203.0.113.45` — публичный IP, `192.168.1.10` — локальный. Оба утекают, если WebRTC не заперт.

Детект через браузер

Самый быстрый способ — открыть консоль на любой странице и запросить ICE-кандидаты. Код ниже работает в Chrome и Firefox без расширений:

```javascript

const pc = new RTCPeerConnection({

iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]

});

pc.createDataChannel('probe');

const found = new Set();

pc.onicecandidate = (e) => {

if (!e.candidate) return;

const parts = e.candidate.candidate.split(' ');

const ip = parts[4];

const type = parts[7];

if (ip && !found.has(ip)) {

found.add(ip);

console.log(type, ip);

}

};

pc.createOffer().then(o => pc.setLocalDescription(o));

setTimeout(() => pc.close(), 5000);

```

Если в выводе видишь свой публичный IP или адрес из домашней подсети — прокси не закрывает WebRTC. Если видишь только `relay`-кандидаты или пустоту — всё чисто.

Второй вариант — `chrome://webrtc-internals` (в Chrome) и `about:webrtc` (в Firefox). Там видно все ICE-сессии, включая те, что открыл не ты. Полезно, когда проверяешь не свою страницу, а чужую — например, сервис, который клянётся «мы не собираем IP».

Детект через curl и tcpdump

Если браузер не под рукой, а проверить надо серверную часть — смотри, какие пакеты реально уходят. Запускаешь tcpdump на интерфейсе, открываешь страницу с WebRTC, смотришь:

```bash

sudo tcpdump -i eth0 -n 'udp port 3478 or udp port 19302 or udp portrange 49152-65535' -c 50

```

STUN ходит на 3478 (стандарт) и 19302 (Google). Если видишь исходящие UDP-пакеты на эти порты в момент, когда весь HTTP идёт через SOCKS5 — это утечка. Пакеты ушли мимо прокси.

Для проверки, что SOCKS5 вообще работает, параллельно смотри TCP:

```bash

curl -x socks5h://127.0.0.1:1080 https://ifconfig.me

```

Флаг `socks5h` важен — он заставляет curl резолвить DNS на стороне прокси. С `socks5://` (без h) DNS уйдёт локально, и это тоже утечка, только DNS-уровня.

Кейс: Firefox 121, SOCKS5 в about:config

Пример: машина с Firefox 121, в настройках выставлен SOCKS5 на `127.0.0.1:1080`, галочка «Proxy DNS when using SOCKS v5» включена. Пользователь открывает тестовую страницу, всё показывает чужой IP. Но `about:webrtc` выдаёт srflx-кандидат с реальным адресом.

Причина: в Firefox параметр `media.peerconnection.ice.proxy_only` по умолчанию `false`. Он отвечает за то, чтобы ICE использовал только прокси-транспорт. Пока флаг выключен, ICE-стек идёт напрямую. Плюс `media.peerconnection.ice.no_host` тоже `false` — host-кандидаты утекают.

Решение: в `about:config` выставить `media.peerconnection.ice.proxy_only = true` и `media.peerconnection.ice.no_host = true`. После перезапуска проверить тем же скриптом — должен остаться только relay или пустой список. Это работает начиная с Firefox 70, но флаги не в UI, и потому их никто не трогает.

Кейс: Chrome 120, расширение-прокси

Другая история: Chrome 120, прокси настроен через расширение (типа Proxy SwitchyOmega), политика `--proxy-server="socks5://127.0.0.1:1080"` не задана. Расширения в Chrome не могут перехватывать WebRTC — API `chrome.proxy` управляет только HTTP-трафиком.

Причина: WebRTC-стек в Chrome изолирован в сетевом сервисе (`network service`), и настройки прокси из расширения до него не доходят. Даже с `--proxy-server` Chrome по умолчанию не блокирует WebRTC — нужен флаг политики.

Решение: запускать Chrome с `--force-webrtc-ip-handling-policy=disable_non_proxied_udp` и `--webrtc-ip-handling-policy=disable_non_proxied_udp`. Второй флаг — для enterprise-политики, первый — для командной строки. Проверить в `chrome://policy` — должна появиться `WebRtcIPHandlingPolicy` со значением `disable_non_proxied_udp`. После этого ICE-кандидаты идут только через прокси-транспорт, а UDP мимо прокси отваливается.

Кейс: nginx как TURN-заглушка

Иногда WebRTC нужен по делу — видеозвонки, стриминг. Тогда простой блок не годится, надо гнать трафик через TURN. Пример: сервер на nginx 1.24 с модулем `nginx-rtmp` и coturn на той же машине. Клиент подключается через SOCKS5, но TURN-сервер должен быть доступен напрямую.

Проблема: если TURN настроен на публичный IP сервера, клиент через прокси всё равно откроет UDP-сессию напрямую к TURN — и это нормально, потому что relay-кандидат укажет адрес TURN, а не клиента. Утечки нет. Но если coturn слушает на `0.0.0.0` без `external-ip`, он подставит внутренний адрес в relay-кандидат, и звонок сломается.

Конфиг coturn:

```

listening-port=3478

external-ip=203.0.113.45

relay-ip=10.0.0.5

min-port=49152

max-port=65535

```

`external-ip` — публичный адрес, который увидят клиенты. `relay-ip` — внутренний, куда coturn биндится. Без этого разделения relay-кандидат будет содержать `10.0.0.5`, и клиент за NAT не сможет до него достучаться. Проверяется тем же скриптом: в srflx/relay должен быть только `203.0.113.45`.

Таблица: что где утекает

| Браузер | Механизм | Флаг блокировки | Где проверить |

|---|---|---|---|

| Firefox 121 | `media.peerconnection.ice.proxy_only` | `true` | `about:config`, `about:webrtc` |

| Chrome 120 | `--force-webrtc-ip-handling-policy` | `disable_non_proxied_udp` | `chrome://policy`, `chrome://webrtc-internals` |

| Edge 120 | наследует Chromium | тот же флаг | `edge://policy` |

| Safari 17 | нет прямого флага | блокировка через профиль | Web Inspector |

Safari — отдельная боль. У него нет пользовательского флага для WebRTC-политики, только enterprise-профиль через MDM или ручное отключение WebRTC целиком. В обычном использовании Safari через SOCKS5 утечка почти гарантирована.

Почему «просто выключить WebRTC» — плохо

Самый популярный совет — расширение, которое убивает `RTCPeerConnection`. Работает, но ломает всё, что на нём построено: видеозвонки в Google Meet, Discord в браузере, часть стриминговых плееров, капчи от Cloudflare (они иногда используют WebRTC для проверки). Получается зоопарк из «работает через раз».

Правильнее — не убивать API, а ограничивать политику ICE. `disable_non_proxied_udp` оставляет WebRTC живым, но заставляет его ходить только через прокси или TURN. Видеозвонки работают, утечки нет.

Если совсем не хочется трогать браузер — вариант с прокси на уровне ОС. Настройка `socks5h` в системном резолвере плюс маршрутизация UDP через TUN-интерфейс (например, через `tun2socks`). Тогда даже прямой UDP-пакет WebRTC уйдёт в туннель. Но это уже не про браузер, а про сетевой стек — и там свои грабли с MTU и фрагментацией.

Проверка на стороне сервера

Если ты держишь сервис и хочешь понять, утекают ли IP твоих пользователей — смотри заголовки и SDP. Сервер, который принимает WebRTC-оффер, видит все кандидаты клиента. Если в `a=candidate` есть адрес не из пула прокси — утечка.

Вот как это выловить в Python на приёме SDP:

```python

import re

def extract_candidates(sdp: str) -> list[tuple[str, str]]:

pattern = r"a=candidate:\S+ \d+ \w+ \d+ ([\d\.]+) \d+ typ (\w+)"

return re.findall(pattern, sdp)

def check_leak(sdp: str, proxy_pool: set[str]) -> list[str]:

leaks = []

for ip, typ in extract_candidates(sdp):

if typ in ("host", "srflx") and ip not in proxy_pool:

leaks.append(f"{typ}:{ip}")

return leaks

```

Прокси-пул — список адресов, которые ты выдаёшь клиентам. Всё, что не из него, — подозрительно. Host-кандидаты из приватных диапазонов (10/8, 172.16/12, 192.168/16) — тоже сигнал: значит, клиент не за прокси или прокси не прикрывает ICE. Такой лог стоит писать в отдельную метрику и алертить, если доля утечек растёт.

Что делать с IPv6

Отдельная история. IPv6-адреса в WebRTC-кандидатах утекают точно так же, а иногда и охотнее: браузеры предпочитают IPv6 при наличии. Если у клиента dual-stack, а прокси только IPv4 — ICE-кандидат с IPv6-адресом уйдёт напрямую, минуя прокси.

Проверять надо отдельно: в `about:webrtc` и `chrome://webrtc-internals` смотри кандидаты с `typ host` и адресом вида `2001:...` или `fe80::`. Если они есть — IPv6 не прикрыт. Решение: либо отключить IPv6 на интерфейсе (грубо, но работает), либо гнать IPv6 через прокси, поддерживающий его. Прокси-сервисы вроде lexic.ml работают с IPv6 с 2015 года — как раз потому, что dual-stack в WebRTC создаёт двойную поверхность атаки, и одной IPv4-поддержки мало.

Итог

WebRTC-утечка — не баг браузера, а следствие архитектуры: ICE-стек живёт отдельно от HTTP-стека и не всегда знает про прокси. Детектится скриптом на `RTCPeerConnection` за пять секунд, проверяется через `about:webrtc` и `chrome://webrtc-internals`, чинится флагами политики ICE, а не отключением API. SOCKS5 сам по себе WebRTC не закрывает — это надо помнить каждый раз, когда настраиваешь браузер «для приватности».

✔️Купить прокси