- Как антифрод-системы вычисляют прокси по TCP fingerprint
Содержание
- Что вообще такое TCP fingerprint
- Почему прокси палится на уровне ядра
- TTL и hop count: первая грубая проверка
- Быстрая проверка TTL до цели
- Или через scapy — точнее, без ICMP-фильтров
- Порядок TCP-опций — уникальный отпечаток
- Window size и масштабирование
- HTTP/2 и TLS fingerprint — следующий слой
- Проверка JA3 через tls-client
- Кейс: парсер маркетплейса на VPS с Linux
- Кейс: бот на Go через HTTP-прокси
- Кейс: residential-прокси с неправильным MTU
- Что реально работает против антифрода
Что вообще такое TCP fingerprint
Когда клиент открывает соединение, ядро отправляет SYN. В этом пакете — куча полей: TTL, размер окна, опции, MSS, масштабирование окна. Приложение к этому не прикасается. Всё делает стек ОС. И вот эти параметры у каждой системы свои.
Linux ставит TTL 64. Windows — 128. Cisco IOS — 255. Это уже сигнал. Но антифрод смотрит не только на TTL. Смотрит на порядок TCP-опций, на значения window scale, на наличие SACK, timestamps, на флаги в IP-заголовке (DF bit). Комбинация этих параметров называется p0f-сигнатурой. И она стабильна для конкретной ОС и версии ядра.
Прокси-серверы ломают эту картину. Клиент думает, что говорит с Windows, а на самом деле SYN пришёл от Linux-прокси с TTL 64. Или наоборот. Антифрод это видит и ставит галочку «подозрительно». Дальше — вопрос весов в скоринговой модели.
Почему прокси палится на уровне ядра
Прокси работает в user space. Он принимает соединение от клиента, потом открывает своё — к целевому серверу. Два разных TCP-стека. Первый — клиентский, второй — серверный. И вот тут начинается зоопарк.
Если прокси на Linux, а клиент на Windows — целевой сервер увидит Linux-сигнатуру. При этом User-Agent в HTTP-заголовках будет говорить «Windows NT 10.0». Несовпадение. Антифрод сравнивает пассивную сигнатуру TCP с заявленным UA. Расхождение — флаг.
Обратный случай: клиент на Linux, прокси на Windows. Тот же эффект, только зеркальный. Особенно заметно на VPS с Windows Server — их меньше, и сигнатура более редкая.
TTL и hop count: первая грубая проверка
TTL уменьшается на каждом маршрутизаторе. Клиент с TTL 64, прошедший через 12 хопов, даст TTL 52 на приёмнике. Антифрод знает: 52 — это скорее всего Linux. А 116 — Windows. 240 — что-то экзотическое.
Прокси с TTL 64, стоящий в одном хопе от целевого сервера, отдаст почти чистые 64. Это выглядит как «Linux в той же стойке». Подозрительно, если заявлен клиент из другого конца мира.
```bash
Быстрая проверка TTL до цели
ping -c 3 -W 2 example.com | grep ttl
Или через scapy — точнее, без ICMP-фильтров
python3 -c "
from scapy.all import IP, TCP, sr1
pkt = IP(dst='example.com')/TCP(dport=443, flags='S')
resp = sr1(pkt, timeout=3, verbose=0)
if resp:
print(f'TTL: {resp.ttl}, window: {resp.window}, flags: {resp.sprintf(\"%TCP.flags%\")}')
"
```
Первая команда даёт грубую оценку. Вторая — точную, потому что SYN-пакет идёт напрямую и TTL в ответе не искажён ICMP-фильтрами. Если TTL ровно 64 или 128 — почти наверняка сервер в том же дата-центре, что и прокси. Либо это сам прокси.
Порядок TCP-опций — уникальный отпечаток
В SYN-пакете опции идут в определённом порядке. Linux 5.x: MSS, SACK permitted, Timestamps, NOP, Window Scale. Windows 10: MSS, NOP, Window Scale, NOP, NOP, SACK permitted. macOS: MSS, NOP, Window Scale, NOP, NOP, Timestamps, SACK permitted, EOL.
Это как отпечаток пальца. p0f строит хеш из порядка опций и их значений. Антифрод-системы используют ту же логику. Если прокси не маскирует стек — сигнатура выдаёт его с головой.
Маскировка возможна, но требует либо патченного ядра, либо userspace TCP-стека (например, через raw sockets). Оба варианта — боль. Первый ломает обновления, второй даёт дикие задержки.
Window size и масштабирование
Размер окна в SYN — ещё один маркер. Linux ставит 29200, 5840 или 64240 в зависимости от версии и настроек. Windows — 65535 с масштабированием. macOS — 65535.
Window scale factor тоже разный. Linux обычно 7. Windows — 8. Это множитель: реальное окно = window * 2^scale. Антифрод смотрит на оба числа.
Прокси, который просто форвардит трафик, не трогает window size. Значит, целевой сервер видит окно прокси, а не клиента. Если клиент заявляет себя как macOS с окном 65535, а приходит 29200 — расхождение.
HTTP/2 и TLS fingerprint — следующий слой
TCP fingerprint — только начало. Дальше идёт TLS ClientHello. Набор шифров, порядок extensions, поддерживаемые группы, ALPN. У Chrome один набор, у curl другой, у Python requests третий.
Антифрод (Cloudflare, Akamai, DataDome) строит JA3-хеш из ClientHello. И сравнивает с известными браузерными. Если User-Agent говорит Chrome 120, а JA3 — от Go-клиента, это красный флаг.
Прокси на уровне TCP не меняет TLS. Значит, TLS-отпечаток идёт от клиента напрямую. Но если клиент — скрипт на Python, а UA подделан под Chrome — палево на TLS-слое.
```python
Проверка JA3 через tls-client
import tls_client
session = tls_client.Session(
client_identifier="chrome_120",
random_tls_extension_order=True
)
resp = session.get("https://tls.browserleaks.com/json")
print(resp.json())
```
Этот код покажет, как сервер видит TLS-отпечаток. Если `ja3_hash` совпадает с браузерным — хорошо. Если нет — антифрод это увидит.
Кейс: парсер маркетплейса на VPS с Linux
Клиент запускал парсер цен с 40 Linux-VPS в разных локациях. User-Agent — Chrome 119 на Windows. Первые два дня — нормально. На третий — бан по IP и CAPTCHA на каждом запросе.
Причина: TCP-сигнатура Linux (TTL 64, window 29200, порядок опций Linux 5.15) против заявленного Windows. Антифрод (похоже на DataDome) сравнил пассивный отпечаток с UA. Расхождение — накопление скора. Плюс TLS JA3 от Python requests — ещё один минус.
Решение: перешли на tls-client с `client_identifier="chrome_119"` и прокси с маскировкой TCP-стека. Прокси-провайдер (не буду называть, но из тех, что дают residential) отдавал трафик через реальные устройства с Windows. TTL 128, window 65535, порядок опций Windows. Скор упал, баны прекратились. Скорость упала с 200 rps до 40 rps — цена маскировки.
Кейс: бот на Go через HTTP-прокси
Сервис на Go ходил через HTTP CONNECT прокси. UA — Firefox 121. Антифрод Cloudflare (Bot Management) начал отдавать 403 на 60% запросов.
Причина: Go-клиент имеет свой TLS-стек. JA3-хеш не совпадал ни с одним браузерным. Плюс TCP-сигнатура — Go использует стандартный Linux-стек, но с другими настройками окна (Go выставляет window 65535 в некоторых версиях). Двойное расхождение.
Решение: заменили HTTP-клиент на uTLS (форк crypto/tls с кастомизацией ClientHello). Выставили Chrome-подобный отпечаток. Прокси — с реальным Windows-стеком на выходе. 403 упали до 4%. Остальные 4% — поведенческие факторы (слишком ровные интервалы между запросами).
Кейс: residential-прокси с неправильным MTU
Клиент использовал residential-прокси для сбора данных с сайта с жёстким антифродом. Прокси-провайдер заявлял «реальные устройства». Но 30% запросов отбивались.
Причина оказалась в MTU. Прокси-устройства сидели за PPPoE с MTU 1492. Клиент — с MTU 1500. При передаче крупных POST-запросов (больше 1400 байт payload) начиналась фрагментация. Антифрод видел нестандартный паттерн фрагментации — не как у обычного браузера с MTU 1500.
Решение: снизили MTU на клиентской стороне до 1400 через `ip link set dev eth0 mtu 1400`. Фрагментация ушла, паттерн стал ровным. Отказы упали до 7%.
Что реально работает против антифрода
Маскировка TCP — необходимая, но недостаточная мера. Нужен полный стек: TCP-сигнатура, TLS-отпечаток, HTTP/2 fingerprint (настройки SETTINGS-фрейма), порядок заголовков, поведенческие паттерны.
Прокси-сервисы, которые дают «чистый» TCP-стек на выходе, стоят дороже. Обычные дата-центровые — палятся на первом же сравнении с UA. Residential — лучше, но качество разное. Некоторые провайдеры (в том числе lexic.ml для IPv6-сценариев) дают возможность контролировать исходящий стек на уровне конфигурации, что снимает часть проблем с несовпадением сигнатур.
Проверять себя стоит регулярно. Простой скрипт на Python с запросами к browserleaks и проверкой TTL/window через scapy даёт быструю обратную связь. Если сигнатуры расходятся — антифрод это увидит раньше, чем вы заметите бан.
```python
import subprocess
from scapy.all import IP, TCP, sr1
def check_fingerprint(host):
pkt = IP(dst=host)/TCP(dport=443, flags='S')
resp = sr1(pkt, timeout=3, verbose=0)
if not resp:
return None
opts = [opt[0] for opt in resp[TCP].options]
return {
'ttl': resp.ttl,
'window': resp.window,
'options': opts,
'mss': dict(resp[TCP].options).get('MSS'),
}
print(check_fingerprint('example.com'))
```
Этот скрипт покажет базовые параметры. Сравните с эталоном для вашей заявленной ОС — расхождения сразу видны. Дальше — либо менять прокси, либо маскировать стек. Третьего пути нет.