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

Как антифрод маркетплейсов палит IPv6-прокси по TLS-отпечатку

Как антифрод маркетплейсов палит IPv6-прокси по TLS-отпечатку

Маркетплейсы смотрят не на IP. Точнее, не только на IP. За последние пару лет фокус сместился: адрес проверить дёшево, но бесполезно — прокси-пулы продаются на каждом углу. А вот TLS ClientHello — это то, что клиент присылает сам, и подделать его сложнее, чем сменить /64.

Дальше разберём, как именно антифрод цепляется за отпечаток, почему JA3 сломался, что даёт JA4 и где IPv6-прокси продолжает светиться.

Что вообще такое TLS-отпечаток и почему он работает

Когда клиент открывает HTTPS-соединение, первым делом он шлёт ClientHello. В нём — версия TLS, набор поддерживаемых шифров (cipher suites), список расширений, эллиптические кривые, форматы точек, ALPN. Порядок и состав этих полей у каждого стека свой. Chrome 120 шлёт один набор, curl 8.5 — другой, python requests — третий.

Сервер видит всё это до всякой аутентификации. Fingerprint строится из стабильных полей: версия, cipher list, extension list, curves, point formats. Хэш от этой строки — и есть идентификатор клиента. Он не зависит от IP, куки, User-Agent. Меняешь прокси — отпечаток остаётся тем же.

Вот тут и начинается боль. Прокси-пул на 10 000 адресов с одним и тем же python-скриптом внутри даёт 10 000 соединений с идентичным JA3. Антифрод видит: разные IP, разные аккаунты, одинаковый отпечаток. Вывод очевиден.

Как маркетплейс применяет отпечаток на практике

Простая проверка: сравнить JA3 с заявленным User-Agent. Если UA говорит «Chrome 121 на Windows», а отпечаток соответствует python-requests — это красный флаг. Не приговор, но триггер для ручной проверки или капчи.

Сложнее: кластеризация. Антифрод группирует сессии по отпечатку и смотрит на поведение внутри кластера. Один отпечаток, 500 аккаунтов, все заходят с разных IPv6-адресов из одной /48 — это уже не совпадение. Даже если каждый адрес выглядит чисто.

Ещё один слой — корреляция с TLS-стеком. Браузер Chrome на Windows и Chrome на Android дают разные отпечатки (разные наборы расширений, разный порядок). Если аккаунт заявляет мобильный трафик, а отпечаток десктопный — вопрос.

Почему IPv6 всё меняет

С IPv4 прокси-пулы обычно маленькие: адреса дорогие, ротация ограничена. С IPv6 провайдер выдаёт /64 — это 2^64 адресов. Прокси-сервисы раздают по адресу на сессию, ротируют хоть каждые 30 секунд. С точки зрения IP-репутации всё чисто: адреса свежие, в чёрных списках не значатся.

Но именно эта лёгкость и подводит. Антифрод давно научился смотреть на префиксы. Если 200 соединений за час пришли из одной /64 — это один ASN, один маршрут, скорее всего один пул. Даже если адреса разные. IPv6-прокси без грамотной ротации по /48 и ASN светится именно на уровне префикса, а не отдельного адреса.

И тут отпечаток добивает. IP-уровень можно размазать по тысяче /64, но если TLS-отпечаток один — кластер собирается обратно.

JA3: как считался и где сломался

JA3 — это MD5 от строки вида `TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats`. Поля склеиваются через дефис, значения — через запятую. Просто, быстро, работало годами.

```

771,4865-4866-4867-49195-49199-49196-49200,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0

```

Проблема в том, что JA3 игнорирует порядок расширений внутри ClientHello и не учитывает ALPN, SNI, signature algorithms. Chrome и Firefox с разными расширениями могут дать одинаковый JA3, если набор совпал. И наоборот — минорное обновление браузера меняет хэш, ломая легитимных пользователей.

Плюс расширения-«пустышки» (GREASE) в Chrome рандомизируют часть полей, и JA3 начинает скакать от соединения к соединению.

JA4: что изменилось

JA4 — переработка от FoxIO, вышла в 2023. Вместо одного хэша — структурированная строка из нескольких частей. Формат примерно такой:

```

t13d1516h2_8daaf6152771_02713d6af862

```

Первая часть — метаданные: `t` (TCP), версия TLS (13), наличие SNI (d — domain), число cipher suites (15), число расширений (16), ALPN (h2). Вторая — хэш отсортированного списка шифров. Третья — хэш расширений и signature algorithms.

Ключевое отличие: JA4 сохраняет порядок расширений и учитывает ALPN. Chrome с h2 и curl с http/1.1 теперь дают разные отпечатки, хотя JA3 у них мог совпасть. Плюс GREASE-значения вырезаются перед хэшированием — отпечаток перестаёт скакать.

Для антифрода это подарок: разрешающая способность выше, ложных срабатываний меньше. Для владельцев прокси — новая головная боль: старые способы маскировки под браузер, настроенные под JA3, перестают работать.

Как выглядит проверка на стороне сервера

Nginx сам по себе JA4 не считает. Нужен модуль или внешний обработчик. Простой вариант — логировать ClientHello через `ssl_preread` и отдавать в анализатор.

```nginx

stream {

log_format tls '\$remote_addr [\$time_local] \$ssl_preread_server_name '

'\$ssl_preread_alpn_protocols';

access_log /var/log/nginx/tls.log tls;

server {

listen 443;

proxy_pass backend:8443;

ssl_preread on;

}

}

```

Для полноценного JA4 нужен парсинг ClientHello. В Python это делается через `scapy` или специализированные библиотеки вроде `ja4`:

```python

from ja4 import JA4

def fingerprint_clienthello(raw_bytes: bytes) -> str:

fp = JA4.from_client_hello(raw_bytes)

return fp.compute()

with open("/tmp/clienthello.bin", "rb") as f:

print(fingerprint_clienthello(f.read()))

```

На практике антифрод маркетплейса держит такой парсер на edge-узле, до балансировщика. Отпечаток пишется в лог вместе с IP, ASN, префиксом /64 и временем. Дальше — агрегация.

Что видит антифрод в логах

Соберём типичную картину. Прокси-пул на IPv6, клиент — python-скрипт с requests, маскируется под Chrome через подмену User-Agent. В логах:

| Поле | Значение |

|---|---|

| ASN | 200373 (пример хостера) |

| IPv6 префикс | 2a0f:xxxx:xxxx::/48 |

| JA4 | t13d1516h2_..._python-специфичный |

| UA | Chrome/121 |

| ALPN | http/1.1 |

UA говорит Chrome, ALPN — http/1.1 вместо h2. Chrome всегда шлёт h2 при доступности. Это первое несовпадение. JA4 не совпадает с эталонным для Chrome 121 — второе. 300 соединений из одной /48 за 10 минут — третье.

Каждое по отдельности — шум. Вместе — уверенный бан.

Подмена отпечатка: что реально работает

Самое надёжное — не подделывать отпечаток, а использовать настоящий браузер. Playwright и Puppeteer с реальным Chromium дают честный JA4. Дорого по ресурсам, но антифрод его не отличит от живого пользователя по TLS.

Второй путь — библиотеки с настраиваемым TLS-стеком. `curl_cffi` умеет имитировать отпечатки Chrome, Safari, Firefox на уровне BoringSSL:

```python

from curl_cffi import requests

resp = requests.get(

"https://example.com/api/items",

impersonate="chrome120",

proxies={"https": "http://[2a0f:xxxx::1]:3128"},

)

print(resp.status_code, resp.json()[:1])

```

Здесь `impersonate="chrome120"` подставляет cipher suites, расширения и ALPN, соответствующие Chrome 120. JA4 будет близок к настоящему. Но не идентичен — версия BoringSSL в curl_cffi отличается от той, что в Chrome, и часть расширений всё равно поплывёт. Антифрод с эталонной базой отпечатков это заметит, если сравнивает побайтово.

Третий путь — mitm-прокси вроде `mitmproxy` с плагином, переписывающим ClientHello. Работает, но хрупко: любое обновление целевого браузера ломает конфиг.

Где IPv6-прокси конкретно подставляется

Разберём три реальных сценария.

**Сценарий с одной /64.** Сервис парсинга цен запускает 50 воркеров через IPv6-прокси, все адреса из одного /64. Антифрод маркетплейса группирует по префиксу, видит 50 сессий с одинаковым JA4 и разными аккаунтами. Бан по префиксу — все 50 адресов уходят в серый список. Решение: ротация по /48 и разным ASN, плюс разные отпечатки на воркер.

**Сценарий с curl_cffi на дефолте.** Клиент использует `impersonate="chrome"` (последняя версия в библиотеке), но она отстаёт от актуального Chrome на 2-3 мажора. Антифрод сверяет JA4 с эталоном Chrome 121, видит расхождение в 4 расширениях. Флаг «возможен автоматизированный клиент». Решение: фиксировать версию impersonate под актуальный Chrome и обновлять раз в месяц.

**Сценарий с requests без маскировки.** Голый python-requests, UA подменён на Safari. JA4 выдаёт python-стек, ALPN http/1.1. Антифрод ловит на первом же запросе к API с авторизацией. Решение: минимум curl_cffi, лучше — реальный браузер под Playwright.

Почему одного отпечатка мало

JA4 — сильный сигнал, но не единственный. Антифрод смотрит ещё на HTTP/2 fingerprint (порядок и значения SETTINGS-фреймов, window size, приоритеты), на порядок заголовков, на TCP-опции (window size, MSS, timestamps). Всё это в сумме даёт портрет клиента, который подделать целиком дорого.

Прокси-сервис на lexic.ml, к примеру, даёт чистые IPv6-адреса с ротацией по /48, но TLS-отпечаток формирует клиент — и если это голый requests, никакой пул не спасёт. Инфраструктура решает половину задачи, вторая половина — на стороне клиента.

Что делать: практический чеклист

Первое — перестать полагаться на подмену UA. Это уже не работает года три как.

Второе — использовать инструменты с настраиваемым TLS-стеком. curl_cffi, tls-client, requests-go. Все они дают отпечатки, близкие к браузерным.

Третье — обновлять профили impersonate вслед за браузерами. Раз в 4-6 недель, не реже.

Четвёртое — ротация IPv6 не по адресу, а по /48 и ASN. Один пул — один префикс — один отпечаток = кластер.

Пятое — мониторить свои отпечатки. Прогнать трафик через `tls.peet.ws` или `browserleaks.com/tls` и сравнить с эталоном целевого браузера. Расхождение в 1-2 расширениях — уже риск.

Итог

Антифрод маркетплейсов давно перестал смотреть только на IP. TLS-отпечаток — стабильный идентификатор клиента, и с приходом JA4 его разрешающая способность выросла. IPv6-прокси дают чистые адреса, но не спасают от кластеризации по отпечатку и префиксу. Работает только комплекс: правильная ротация, честный TLS-стек, актуальные профили браузеров. Всё остальное — костыли, которые отваливаются на первом же обновлении антифрода.

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