Как антифрод выявляет IPv6-прокси по TTL и MSS в TCP-хендшейке
Содержание
- Что вообще видно в SYN-пакете
- TTL как индикатор хопов
- MSS и MTU: где прокси палится чаще всего
- Согласованность опций TCP
- Как это выглядит в реальном трафике
- Практика: снять сигнатуру самостоятельно
- Почему IPv6-прокси детектится легче IPv4
- Таблица типичных сигнатур
- Кейс: прокси-пул с ротацией на Hetzner
- Кейс: мобильный оператор с CGNAT
- Кейс: туннель через Hurricane Electric
- Что делать, если вы на стороне прокси
- Итог
TCP-хендшейк — это первые три пакета соединения. SYN, SYN-ACK, ACK. Меньше 200 байт трафика в обе стороны. И этого хватает, чтобы отличить прямой коннект от прокси с точностью, которая многих удивляет. Антифрод-системы давно научились читать сигнатуры на этом уровне. Не по геолокации IP, не по User-Agent — по служебным полям, которые операционная система ставит автоматически и которые прокси-софт часто не умеет подделывать согласованно.
Что вообще видно в SYN-пакете
Входящий SYN несёт в заголовке TCP кучу полей. Для фрод-аналитики важны четыре: TTL из IP-заголовка, MSS из опций TCP, window size и набор самих опций (их порядок и наличие). Плюс для IPv6 — hop limit, который функционально то же самое, что TTL в IPv4, только называется иначе. Стек отправляет эти значения по умолчанию, и они зависят от ОС, версии ядра и сетевых настроек.
Дальше работает простая логика. Если клиент пришёл с TTL=64, MSS=1460 и окном 65535 — это похоже на Linux. Если TTL=128, MSS=1460, окно 8192 — Windows. Если TTL=255 и странный набор опций — возможно, сетевое оборудование или хитрый прокси. Само по себе совпадение ничего не доказывает. Но несовпадение с заявленным отпечатком (например, User-Agent говорит Windows 11, а TCP-стек — Linux) уже даёт сигнал.
Проблема в том, что NAT и балансировщики тоже меняют поля. Поэтому антифрод смотрит не на абсолютные значения, а на их согласованность между собой и с другими слоями.
TTL как индикатор хопов
TTL уменьшается на единицу на каждом маршрутизаторе. Начальное значение задаёт ОС-отправитель. Пришёл пакет с TTL=52 — значит, где-то по пути было 12 хопов от источника с дефолтом 64. Или 76 хопов от источника с дефолтом 128. Первое куда правдоподобнее.
Антифрод строит гипотезу: `initial_ttl = observed_ttl + hops`. Точное число хопов неизвестно, но оно ограничено. Если клиент заявляет себя как пользователь из Германии, а пакет пришёл с TTL=250 (то есть почти не проходил через маршрутизаторы), это странно. Либо источник в той же подсети, либо TTL подделан, либо это локальный прокси-сервер, который принял соединение и переслал данные, сохранив свой собственный TTL.
Прокси-серверы в этом месте ломаются по-разному. Прозрачный SOCKS5 на Linux пересоздаёт TCP-соединение к апстриму со своим стеком — TTL будет 64 минус хопы от прокси до сервера. HTTP-прокси (Squid, 3proxy) делает то же самое. А вот клиент, сидящий за прокси, приходит к антифроду уже с TTL прокси, не со своим. И если прокси в датацентре за три хопа от целевого сервера, TTL будет 61-63. Живой пользователь из той же страны редко даёт такую картину — у него 10-20 хопов минимум.
MSS и MTU: где прокси палится чаще всего
MSS (Maximum Segment Size) объявляется в опции TCP при установке соединения. Это максимальный размер полезной нагрузки в одном сегменте. Стандарт для Ethernet с MTU 1500 — MSS 1460 (1500 минус 20 байт IP-заголовка минус 20 байт TCP-заголовка). Для IPv6 заголовок больше — 40 байт вместо 20, поэтому честный MSS для IPv6 на том же MTU = 1440.
Вот тут и начинается зоопарк. Linux по умолчанию для IPv6 отправляет MSS 1440. Windows — тоже 1440. А вот многие прокси-демоны и туннели работают через IPv4-аплинк и транслируют соединение так, что клиент видит MSS 1460 в IPv6-сессии. Это физически невозможно на честном IPv6-канале с MTU 1500 без фрагментации. Антифрод такое ловит моментально: IPv6-соединение с MSS 1460 — почти наверняка прокси или туннель.
Есть и вторая сигнатура — MSS ниже нормы. Скажем, 1360 или 1240. Это типично для PPPoE (MTU 1492 → MSS 1432 для IPv4, 1412 для IPv6), для VPN (WireGuard с MTU 1420 → MSS 1360/1380), для некоторых мобильных операторов. Само по себе не приговор. Но в сочетании с TTL=64 и датацентровым IP — уже подозрительно.
Третья сигнатура — MSS, который не меняется между IPv4 и IPv6. Если клиент приходит по IPv6 с MSS 1460, а его IPv4-сессия (если есть) показывает тот же 1460 — значит, IPv6-туннель, а не нативный стек. Нативный стек обязан урезать MSS на 20 байт из-за разницы в размере заголовков.
Согласованность опций TCP
Помимо MSS, в SYN приходит набор опций: SACK permitted, Timestamps, Window Scale, NOP-заполнители. Их порядок и наличие — это отпечаток стека. p0f и аналогичные инструменты строят по ним сигнатуры уже лет пятнадцать.
Linux 5.x отправляет опции в порядке: MSS, SACK, TS, NOP, WS. Windows 10/11: MSS, NOP, WS, NOP, NOP, SACK. macOS: похоже на BSD-семейство, с своими нюансами. Прокси, который терминирует соединение и открывает новое, приносит опции своего стека. Если прокси на Linux, а клиент заявляет macOS — расхождение.
Антифрод сверяет это с TLS ClientHello (JA3/JA4), с HTTP/2-настройками (SETTINGS frame, приоритеты), с порядком заголовков в HTTP-запросе. Всё это должно указывать на одну ОС. Когда TCP говорит Linux, TLS говорит Windows, а User-Agent — macOS, система ставит высокий скор фрода. Не блокирует сразу, но помечает.
Как это выглядит в реальном трафике
Практический пример: сервер на nginx 1.24 принимает IPv6-соединения. В логах видно, что часть клиентов приходит с TTL 63-64, MSS 1440, окно 64800, полный набор Linux-опций. Но TLS-отпечаток у них — Chrome на Windows. И IP из датацентра Hetzner. Три несовпадения из трёх.
Разбор через tcpdump показывает: соединение установлено, но keep-alive пакеты идут с интервалом ровно 75 секунд. Это дефолт Linux-ядра, не Windows. Windows шлёт keep-alive раз в 2 часа по умолчанию. Прокси-сервер на Linux просто пересоздал соединение и унаследовал свои таймеры.
Второй слой проверки — поведение при повторной передаче. Linux и Windows по-разному реализуют RTO (retransmission timeout) и backoff. При потере пакета Linux удваивает таймаут, начиная с 200 мс, Windows — с 300 мс и с другим паттерном. Антифрод может специально спровоцировать потерю (отправить пакет с маленьким окном или дропнуть ACK) и посмотреть на реакцию. Прокси с Linux-стеком отреагирует как Linux, даже если клиент — Windows.
Практика: снять сигнатуру самостоятельно
Смотрим, что приходит на наш сервер. Простой сниффер на Python через scapy:
```python
from scapy.all import sniff, IP, IPv6, TCP
def inspect(pkt):
if not pkt.haslayer(TCP):
return
tcp = pkt[TCP]
if not (tcp.flags & 0x02):
return
if pkt.haslayer(IPv6):
src, ttl = pkt[IPv6].src, pkt[IPv6].hlim
proto = "IPv6"
else:
src, ttl = pkt[IP].src, pkt[IP].ttl
proto = "IPv4"
mss = None
for opt in tcp.options:
if opt[0] == "MSS":
mss = opt[1]
print(f"{proto} {src} ttl={ttl} mss={mss} win={tcp.window} opts={[o[0] for o in tcp.options]}")
sniff(filter="tcp[tcpflags] & tcp-syn != 0", prn=inspect, count=20)
```
Запускаем на сервере, смотрим на входящие SYN. Через минуту будет картина: сколько клиентов с какими TTL, какие MSS, какие наборы опций. Сразу видно выбросы — кластеры с одинаковыми сигнатурами из разных подсетей.
Проверить свой собственный отпечаток снаружи можно через curl с verbose:
```bash
curl -6 -v --max-time 5 https://ifconfig.co/ 2>&1 | grep -E "Trying|Connected|SSL"
```
Выведет IP, порт, версию TLS. Полезно для понимания, что видит удалённая сторона. А для глубокого разбора — `ss -tin` на локальной машине показывает MSS, cwnd, rtt для активных соединений.
Почему IPv6-прокси детектится легче IPv4
Парадокс, но факт. IPv4-прокси научились маскироваться давно, потому что рынок большой и старый. IPv6-прокси — ниша поменьше, инструментов меньше, качество разное. Плюс сам протокол даёт больше информации.
В IPv6 нет NAT в привычном смысле. Каждый адрес — глобально маршрутизируемый (если провайдер не делает NAT66, что редкость). Это значит, что по префиксу адреса можно точнее определить AS и тип сети. Датацентровые префиксы (Hetzner, OVH, DigitalOcean) известны. Жилые — тоже. Прокси из датацентра с IPv6 сразу виден по AS.
Второй момент — privacy extensions (RFC 4941). Нативные IPv6-клиенты часто используют временные адреса, которые меняются каждые несколько часов. Прокси обычно сидит на статическом адресе. Если один и тот же IPv6-адрес приходит от «разных пользователей» с разными cookies и User-Agent в течение суток — это прокси-пул или один прокси с ротациейidentity, но не адреса.
Третий — обратная DNS и whois. У жилых IPv6-префиксов часто есть PTR-записи вида `dynamic-xxx.provider.net`. У датацентров — `static.hetzner.com` или вообще ничего. Антифрод сверяет PTR с заявленной геолокацией.
Таблица типичных сигнатур
| ОС / среда | Начальный hop limit (IPv6) | MSS IPv6 | Window | Опции SYN |
|---|---|---|---|---|
| Linux 5.x/6.x | 64 | 1440 | 64800 | MSS,SACK,TS,NOP,WS |
| Windows 10/11 | 128 | 1440 | 65535 | MSS,NOP,WS,NOP,NOP,SACK |
| macOS 13+ | 64 | 1440 | 65535 | MSS,NOP,WS,NOP,NOP,TS,SACK |
| FreeBSD 13 | 64 | 1440 | 65535 | MSS,NOP,WS,SACK,TS |
| OpenWRT (роутер) | 64 | 1420-1440 | 5840 | MSS,SACK,TS,NOP,WS |
| WireGuard-туннель | 64 | 1360-1380 | 64800 | MSS,SACK,TS,NOP,WS |
| Прозрачный SOCKS5 на Linux | 64 | 1440 | 64800 | как у Linux |
| HTTP-прокси 3proxy | 128 | 1460 | 65535 | как у Windows-сервера |
Цифры приблизительные, зависят от версии ядра и sysctl. Но общая картина такая: несоответствие MSS между IPv4 и IPv6, несоответствие hop limit ожидаемому для заявленной ОС, несоответствие набора опций — всё это сигналы.
Кейс: прокси-пул с ротацией на Hetzner
Клиент жалуется: регистрации проходят, но через сутки аккаунты блокируются. Смотрим логи антифрода. Все регистрации — с IPv6 из префикса 2a01:4f8::/32 (Hetzner). TTL 63. MSS 1440. Опции Linux. User-Agent — смесь Chrome на Windows и Safari на macOS.
Причина: прокси-пул на Hetzner, все ноды на Linux, клиентский софт подставляет случайный User-Agent, но не трогает TCP-стек. Антифрод видит три несовпадения: датацентровый AS, Linux-сигнатуру при заявленном Windows/macOS, и одинаковый TTL у всех «разных» пользователей.
Решение: либо использовать резидентные IPv6-прокси (дорого, но работает), либо согласовать отпечатки — поднимать прокси на Windows-серверах для Windows-UA, на macOS-хостах для Safari-UA. Второй вариант дороже в поддержке, но даёт лучший результат. Третий путь — использовать прокси с полной эмуляцией стека, где TCP-опции, TLS-отпечаток и HTTP-заголовки согласованы. Такие решения есть, стоят денег, но проблему закрывают.
Кейс: мобильный оператор с CGNAT
Обратная ситуация. Антифрод блокирует живых пользователей мобильного оператора. Смотрим: IPv6-адреса из префикса оператора, TTL 58-62 (много хопов), MSS 1360-1380 (типично для LTE с туннелями GTP-U), окно 65535, опции Windows. Всё сходится — это реальные пользователи на Windows-смартфонах через мобильную сеть.
Причина ложного срабатывания: антифрод настроен на «MSS ниже 1440 = подозрительно». Но мобильные сети легитимно дают пониженный MSS из-за туннелей и фрагментации в ядре оператора. Плюс CGNAT — сотни пользователей за одним IPv6-префиксом.
Решение: добавить в правила исключение для известных мобильных AS (MTS, Beeline, Megafon, Tele2 и их IPv6-префиксы). Смотреть не на абсолютное значение MSS, а на его стабильность в рамках сессии. И учитывать, что мобильный трафик часто меняет адрес в пределах /64 — это норма.
Кейс: туннель через Hurricane Electric
Разработчик настроил IPv6 через HE Tunnelbroker (6in4). MTU туннеля — 1480, значит MSS для IPv6 = 1420. Приходит на сервер: TTL 60-62, MSS 1420, опции Linux, IP из префикса HE (2001:470::/32).
Антифрод видит: датацентровый префикс HE, пониженный MSS, TTL ниже ожидаемого для нативного IPv6. Ставит средний скор. Не блокирует, но требует капчу на каждую вторую страницу.
Причина: HE Tunnelbroker — легитимный сервис, но им пользуются и фродеры, потому что бесплатно и даёт IPv6-адрес в любой стране (по выбору туннельного сервера). Антифрод не может отличить разработчика от фродера по одному префиксу.
Решение для владельца сервиса: не блокировать HE целиком, но повышать требования к аутентификации для трафика из известных туннельных префиксов. Для разработчика — использовать нативный IPv6 от провайдера или платный туннель с резидентным адресом. Если совсем никак — согласовать MSS: выставить MTU туннеля 1500 с фрагментацией на уровне приложения, чтобы MSS был 1440 как у нативного стека. Костыль, но работает.
Что делать, если вы на стороне прокси
Если задача — не палиться, а не блокироваться, придётся поработать. Первое: согласовать MSS. На IPv6-интерфейсе выставить MTU 1500 и убедиться, что ядро отправляет MSS 1440. Проверить через `ip -6 route show` и `sysctl net.ipv6.conf.all.mtu`. Второе: согласовать TTL. Если прокси в датацентре, TTL будет низким — либо использовать прокси ближе к пользователям (резидентные сети), либо мириться с сигналом. Третье: согласовать опции TCP. Тут сложнее — придётся либо использовать прокси с настраиваемым стеком, либо эмулировать на уровне приложения (что почти невозможно без патча ядра).
Некоторые провайдеры IPv6-прокси, включая lexic.ml, дают возможность выбора геолокации и типа сети — это снижает вероятность попадания в датацентровые фильтры, но не убирает проблему TTL и MSS полностью. Полная маскировка требует комплексного подхода: согласованные TCP-опции, TLS-отпечаток, HTTP-заголовки и поведенческие паттерны. По отдельности каждый слой пробивается, вместе — уже нет.
Итог
TCP-хендшейк — маленький, но информативный. TTL говорит о расстоянии и типе сети, MSS — о MTU и типе канала, опции — об ОС. Антифрод сверяет это с TLS, HTTP и поведением. Когда всё сходится — клиент проходит. Когда нет — попадает в серую зону или блокируется. IPv6-прокси в этой игре проигрывают чаще IPv4, потому что инструментов меньше, а сигналов больше. Хотите не палиться — работайте над согласованностью всех слоёв, а не только над заменой IP.