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

Почему WebRTC сливает реальный IP через SOCKS5 и как его глушить на уровне браузера

Почему WebRTC сливает реальный IP через SOCKS5 и как его глушить на уровне браузера

SOCKS5 проксирует TCP и UDP, но браузеру плевать на ваши настройки прокси, когда речь заходит о WebRTC. ICE-кандидаты собираются напрямую через сетевые интерфейсы. Хост-кандидаты содержат локальные адреса, а вот srflx-кандидаты через STUN уже палят публичный IP. И если UDP идёт мимо прокси — привет, деанонимизация.

Что вообще происходит под капотом WebRTC

WebRTC строит соединение через ICE (Interactive Connectivity Establishment). Браузер собирает кандидатов трёх типов: host (локальный интерфейс), srflx (публичный адрес через STUN-сервер), relay (через TURN). Для сбора srflx браузер шлёт STUN Binding Request на UDP-порт 3478. Пакет уходит с того интерфейса, который выбрала операционка по таблице маршрутизации.

SOCKS5 умеет UDP ASSOCIATE, но браузеры эту возможность почти не используют для WebRTC. Chrome, Firefox, Edge — все они игнорируют системный прокси в контексте ICE-сбора. Точнее, частично: TCP-кандидаты (редкость) могут пойти через прокси, но UDP-трафик STUN уходит напрямую. Отсюда и утечка.

Механика простая: STUN-сервер отвечает с вашим публичным адресом и портом. Браузер включает эту пару в SDP как srflx-кандидат. Любой, кто получит SDP (а его получает вторая сторона соединения и часто — сигнальный сервер), узнаёт ваш реальный IP.

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

Ключевая проблема — уровень абстракции. SOCKS5 работает на уровне приложений, проксируя соединения по запросу клиента. Но WebRTC-стек в браузере общается с сетевым слоем напрямую через сокеты ОС. Он не спрашивает разрешения у прокси-настроек. Он просто открывает UDP-сокет и шлёт пакет.

Chrome с флагом `--proxy-server="socks5://127.0.0.1:1080"` всё равно собирает srflx-кандидаты напрямую. Проверялось на версиях 118-124. Firefox с `network.proxy.socks_remote_dns=true` ведёт себя чуть аккуратнее с DNS, но ICE-сбор всё равно идёт мимо.

Есть нюанс: если в системе нет маршрута наружу кроме прокси (например, в изолированном network namespace), то STUN просто не получит ответ. Но это уже не про прокси, а про сетевую изоляцию.

Как выглядит утечка в цифрах

Возьмём типичный сценарий. Пользователь сидит за SOCKS5 на 127.0.0.1:1080, реальный IP 203.0.113.45, прокси-выход 198.51.100.7. Открывает страницу с WebRTC-звонком. Замеры на Chrome 122:

| Кандидат | Адрес | Источник | Виден второй стороне |

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

| host | 192.168.1.42 | локальный интерфейс | да |

| host | fe80::1c2b:3f4a | IPv6 link-local | да |

| srflx | 203.0.113.45:54321 | STUN напрямую | да |

| relay | 198.51.100.7:49152 | TURN через прокси | да |

Второй и третий — катастрофа. Локальный адрес выдаёт топологию сети, srflx — публичный IP. Даже если сигнальный сервер HTTPS, SDP с кандидатами уходит в открытом виде внутри signalling-протокола. Многие приложения логируют SDP целиком.

Отключение WebRTC в Chrome и Chromium

Самый чистый способ — policy или флаг. Для управляемых окружений есть `WebRtcIPHandlingPolicy`. Значения: `default`, `default_public_interface_only`, `default_public_and_private_interfaces`, `disable_non_proxied_udp`. Последнее — то, что нужно.

Через реестр Windows:

```

[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome]

"WebRtcIPHandlingPolicy"="disable_non_proxied_udp"

```

Через командную строку при запуске:

```bash

chromium \

--proxy-server="socks5://127.0.0.1:1080" \

--force-webrtc-ip-handling-policy=disable_non_proxied_udp \

--webrtc-ip-handling-policy=disable_non_proxied_udp

```

Флаг `disable_non_proxied_udp` заставляет браузер использовать только проксируемые UDP-пути. Если прокси не поддерживает UDP ASSOCIATE — WebRTC просто не поднимется. Это компромисс: звонки не работают, зато IP не течёт.

Есть ещё `--disable-features=WebRtcHideLocalIpsWithMdns` — наоборот, включает mDNS-обфускацию локальных адресов. Chrome по умолчанию с версии 76 маскирует host-кандидаты через mDNS (`.local` имена). Но srflx всё равно палится.

Firefox: настройки about:config

В Firefox управление через `media.peerconnection.ice.*`. Ключевые параметры:

```

media.peerconnection.ice.default_address_only = true

media.peerconnection.ice.no_host = true

media.peerconnection.ice.proxy_only_if_behind_proxy = true

media.peerconnection.enabled = false

```

`proxy_only_if_behind_proxy` заставляет ICE использовать только прокси-интерфейс, если прокси настроен. `no_host` убирает host-кандидаты. Полное отключение `peerconnection.enabled=false` убивает WebRTC целиком.

Проверка через `about:webrtc` показывает собранные кандидаты в реальном времени. Запустите тест на `browserleaks.com/webrtc` — увидите, что утекает.

Когда прокси всё-таки держит WebRTC

Сценарий: SOCKS5 с поддержкой UDP ASSOCIATE плюс браузер, который умеет в него ходить. На практике это редкость. Chromium не поддерживает UDP через SOCKS5 для WebRTC. Firefox тоже. Единственный рабочий путь — TURN-сервер, который сам ходит наружу через нужный интерфейс.

Схема такая: браузер настраивается на TURN по TCP (порт 443, чтобы пробивать файрволы), TURN-сервер стоит на VPS с нужным IP, весь relay-трафик идёт через него. Тогда srflx-кандидаты либо не собираются (если STUN заблокирован), либо указывают на TURN. Настройка в Firefox:

```

media.peerconnection.ice.relay_only = true

```

Это заставляет использовать только relay-кандидаты. Медиа пойдёт через TURN, задержка вырастет на 30-80 мс в зависимости от расстояния до сервера, зато IP чистый. Для голосовых звонков терпимо, для игр — уже чувствуется.

Проверка утечек: скрипт на Python

Соберём автоматическую проверку. Скрипт поднимает headless Chrome с прокси и парсит кандидатов через CDP.

```python

import asyncio

import json

import websockets

async def check_webrtc(ws_url):

async with websockets.connect(ws_url) as ws:

await ws.send(json.dumps({

"id": 1,

"method": "Runtime.evaluate",

"params": {

"expression": """

(async () => {

const pc = new RTCPeerConnection({

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

});

pc.createDataChannel('probe');

const cands = [];

pc.onicecandidate = e => {

if (e.candidate) cands.push(e.candidate.candidate);

};

await pc.setLocalDescription(await pc.createOffer());

await new Promise(r => setTimeout(r, 3000));

pc.close();

return cands;

})()

""",

"awaitPromise": True,

"returnByValue": True

}

}))

while True:

msg = json.loads(await ws.recv())

if msg.get("id") == 1:

return msg["result"]["result"]["value"]

if __name__ == "__main__":

import sys

print(asyncio.run(check_webrtc(sys.argv[1])))

```

Запуск Chrome с remote debugging:

```bash

chromium --headless=new --remote-debugging-port=9222 \

--proxy-server="socks5://127.0.0.1:1080" \

--force-webrtc-ip-handling-policy=disable_non_proxied_udp \

about:blank

```

Скрипт покажет список кандидатов. Если в выводе есть srflx с адресом, отличным от IP прокси — утечка. `disable_non_proxied_udp` должен оставить только relay-кандидаты или пустой список.

Кейс: корпоративный терминальный сервер

Терминальный сервер на Windows Server 2019, 40 пользователей, все ходят через SOCKS5 на шлюзе. Один пользователь открыл личную почту с веб-камерой — WebRTC-виджет отправил srflx-кандидат с внутренним адресом 10.20.5.17 и публичным IP офиса. Проблема всплыла при аудите: в логах внешнего сервиса засветился публичный IP компании рядом с учёткой сотрудника.

Причина: Chrome 119 на терминале без групповых политик. Пользовательские настройки прокси не применялись к WebRTC, потому что политика `WebRtcIPHandlingPolicy` не была задана. Решение: раскатали GPO с `disable_non_proxied_udp`, плюс отключили WebRTC на уровне расширения для тех, кому звонки не нужны. Через неделю проверили — srflx-кандидатов ноль, только mDNS-имена вида `a1b2c3d4-....local`.

Кейс: антидетект-браузер и IPv6-прокси

Связка антидетект-браузера с SOCKS5 поверх IPv6. Пользователь думал, что прокси скрывает всё. Но WebRTC собрал host-кандидат с IPv6 link-local `fe80::a4b5:c6d7` и srflx через STUN с IPv4-адресом провайдера. Второй адрес совпал с реальным — деанонимизация за две минуты.

Причина: антидетект-браузер на базе Chromium 117 не применял флаг `--force-webrtc-ip-handling-policy` к дочерним процессам. WebRTC-стек работал в renderer-процессе с дефолтными настройками. Решение: добавили флаг в аргументы всех процессов через `--webrtc-ip-handling-policy=disable_non_proxied_udp` и проверили через `chrome://version` — флаг виден в командной строке. Плюс настроили IPv6-прокси через lexic.ml, где UDP-трафик изолирован на уровне маршрутизации, и STUN-запросы просто не находят маршрут наружу. После этого тест на `browserleaks.com/webrtc` показал пустой список кандидатов.

Кейс: видеоконференция через корпоративный прокси

Компания на 200 человек, все через SOCKS5 на порту 1080. Zoom в браузере работал, но качество падало. При разборе выяснилось: браузер собирал srflx-кандидаты напрямую, а relay-кандидаты через TURN-сервер в другом регионе. ICE выбирал прямой путь (srflx), трафик шёл мимо прокси. Задержка 15 мс, но весь медиапоток — вне корпоративного контроля.

Причина: политика `default` в Chrome. Браузер предпочитает прямые пути. Решение: включили `relay_only` через политику `WebRtcIPHandlingPolicy=default_public_interface_only` плюс принудительный TURN через `iceTransportPolicy: 'relay'` в конфиге приложения. Задержка выросла до 55 мс, зато весь трафик пошёл через корпоративный TURN на 443/TCP. Пропускная способность TURN-сервера — 500 Мбит/с, хватило с запасом.

Что делать, если WebRTC нужен

Полное отключение — грубо. Если звонки критичны, есть компромисс: TURN-only режим с релеем на нужном IP. Настройка в приложении:

```javascript

const pc = new RTCPeerConnection({

iceServers: [

{ urls: 'turn:turn.example.com:443?transport=tcp',

username: 'user', credential: 'pass' }

],

iceTransportPolicy: 'relay'

});

```

`iceTransportPolicy: 'relay'` заставляет ICE использовать только relay-кандидаты. Браузер не собирает host и srflx. Весь медиапоток идёт через TURN. Задержка растёт, но IP не течёт. TURN-сервер на 443/TCP пробивает почти любые файрволы, включая корпоративные.

Для тех, кому нужен именно SOCKS5 и звонки — только сеть без прямого маршрута наружу. Network namespace, где единственный выход — прокси. Тогда STUN-пакеты просто дропаются, ICE остаётся без srflx. Жёстко, но работает без правок браузера.

Итоговая таблица методов

| Метод | Chrome | Firefox | Звонки работают | Сложность |

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

| `disable_non_proxied_udp` | да | нет | нет | низкая |

| `peerconnection.enabled=false` | через флаг | да | нет | низкая |

| TURN-only (`relay`) | да | да | да, с задержкой | средняя |

| Сетевая изоляция | да | да | нет | высокая |

| mDNS-обфускация | по умолчанию | нет | да | нулевая |

mDNS прячет только локальные адреса. srflx он не трогает. Полагаться на него как на защиту — ошибка.

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