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

Как антифрод выявляет IPv6-прокси по TTL и MSS в TCP-хендшейке

Как антифрод выявляет IPv6-прокси по TTL и MSS в TCP-хендшейке

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.

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