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

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

Прокси на уровне TCP — это ещё не анонимность. Антифрод маркетплейсов давно перестал смотреть только на IP. Ему интереснее, как именно твой клиент здоровается с сервером. TLS-хендшейк — это визитная карточка софта, и по ней видно больше, чем по User-Agent.

JA3 — это хеш из полей ClientHello. Версия TLS, набор шифров, список расширений, эллиптические кривые, форматы точек. Всё это складывается в строку и хешируется в MD5. Два клиента с одинаковым JA3 почти наверняка собраны на одном стеке. Разные — почти наверняка нет.

Маркетплейс видит: с одного IP приходят запросы с JA3, который не встречается ни у одного реального браузера. Или встречается, но в паре с User-Agent от Chrome 120, а сам JA3 соответствует Python requests. Это уже сигнал. Дальше — вопрос весов в скоринге.

Почему одного IP недостаточно

Раньше антифрод жил на чёрных списках IP и ASN. Купил резидентный прокси — и вроде свободен. Сейчас резидентных пулов столько, что блокировать по IP дорого: зацепишь живых покупателей. Поэтому детект сместился на слой приложения и транспорт.

IP остался как один из факторов. Но если у тебя чистый резидентный адрес и при этом TLS-хендшейк от curl 8.5 — тебя вычислят быстрее, чем по геолокации. Антифрод-команды маркетплейсов (Ozon, Wildberries, Amazon, Allegro) строят профили устройств и клиентов, и TLS — один из самых стабильных признаков.

Стабильность — ключевое. User-Agent подделать легко, заголовки тоже. А ClientHello формирует TLS-библиотека, и её отпечаток меняется только при смене версии или патча. Это делает JA3 хорошим якорем для корреляции.

Что вообще лежит в ClientHello

ClientHello — первое сообщение TLS-хендшейка. Клиент перечисляет, что умеет: версии протокола, cipher suites, расширения (SNI, ALPN, supported_groups, signature_algorithms и десятки других). Порядок и состав этих полей зависит от библиотеки.

OpenSSL 3.0 и BoringSSL (Chrome) дают разные наборы. Go crypto/tls — свой. Python ssl поверх OpenSSL — почти как OpenSSL, но с нюансами. Node.js на OpenSSL — снова вариации. Именно поэтому JA3 различает не «браузер vs не браузер», а конкретные реализации.

JA3-строка выглядит так: `TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats`. Каждое поле — дефис-разделённый список чисел. Хеш MD5 от строки — это и есть отпечаток.

```

771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0

```

Это фрагмент реального JA3 Chrome. Видишь расширение 17513 (application_settings) — оно есть у Chromium-браузеров и почти ни у чего больше. Один такой маркер уже режет половину ботов.

JA3 vs JA3N vs JA4

JA3 придумали в Salesforce в 2017-м. Проблема вылезла быстро: порядок расширений в ClientHello у части клиентов рандомизируется (Chrome с версии ~110 начал перемешивать extension order для anti-fingerprinting). Хеш «поплыл».

Появился JA3N — та же логика, но расширения и шифры отсортированы перед хешированием. Устойчивее к перемешиванию, но теряет часть энтропии. Дальше FoxIO сделали JA4 — уже не MD5, а структурированная строка с префиксами: версия, SNI-флаг, число шифров, ALPN, расширения. JA4 читаемее и стабильнее.

| Метод | Год | Хеш | Устойчив к shuffle | Читаемость |

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

| JA3 | 2017 | MD5 | нет | низкая |

| JA3N | 2020 | MD5 | да | низкая |

| JA4 | 2023 | SHA256-усечённый | да | высокая |

Маркетплейсы пока массово сидят на JA3 + JA3N. JA4 внедряют крупные CDN (Cloudflare, Akamai) и постепенно подтягивают антифрод-вендоры вроде Forter и Sift. Если твой прокси-стек не даёт консистентный JA4 с браузером — это тоже флаг.

Как антифрод это использует на практике

Схема простая. На edge-сервере (nginx/Envoy/Cloudflare) снимается JA3 каждого входящего коннекта. Дальше — три вещи: матчинг с известными отпечатками, корреляция с остальными сигналами, аномалия по частоте.

Матчинг: есть база JA3 популярных браузеров и версий. Если пришёл отпечаток, которого нет в базе — балл риска. Если он есть, но не совпадает с заявленным User-Agent — балл выше. Chrome 121 с JA3 от Python — классика.

Корреляция: один и тот же JA3 с 500 разных IP за час? Это ферма. Один IP с двадцатью разными JA3 за минуту? Тоже подозрительно — живой юзер так не делает. Антифрод смотрит на распределение.

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

Как снять JA3 на своей стороне

Хочешь проверить, что отдаёт твой прокси-стек — снимай отпечаток сам. На nginx 1.24 с модулем `nginx-ssl-ja3` (или на Envoy с фильтром) это делается в пару строк.

```nginx

server {

listen 443 ssl;

server_name example.com;

ssl_certificate /etc/ssl/cert.pem;

ssl_certificate_key /etc/ssl/key.pem;

location / {

add_header X-JA3 \$http_ssl_ja3 always;

proxy_pass http://backend;

}

}

```

Модуль парсит ClientHello и кладёт строку в переменную. Дальше её можно логировать и сравнивать. Если хочешь сырой разбор — Python с `scapy` или `tls-parser` тоже справится, но для продакшена лучше готовый модуль: меньше шансов накосячить с парсингом фрагментированных хендшейков.

Логируй JA3 вместе с IP, User-Agent, ALPN и SNI. Без этих полей отпечаток бесполезен — нужен контекст. И не забудь про HTTP/2: там TLS-слой тот же, но ALPN другой, и это тоже часть сигнатуры.

Python-клиент и его JA3

Стандартный `requests` на CPython 3.11 с OpenSSL 3.0 даёт стабильный JA3, который сильно отличается от браузерного. Проверить легко — подними локальный сервер и сравни.

```python

import ssl, socket, hashlib

def ja3_of(host, port=443):

ctx = ssl.create_default_context()

with socket.create_connection((host, port)) as sock:

with ctx.wrap_socket(sock, server_hostname=host) as ssock:

cipher = ssock.cipher()

version = ssock.version()

return version, cipher[0]

print(ja3_of("example.com"))

```

Этот код покажет версию и шифр, но не полный JA3 — для полного нужен перехват ClientHello. Практический вывод другой: `requests` и `httpx` ходят через OpenSSL, и их отпечаток палится моментально. Если прокси-пул гоняет трафик через `requests` — все IP будут светиться одним JA3.

Решение — либо `curl_cffi` (эмуляция браузерных отпечатков), либо реальный headless-браузер через CDP, либо специализированные TLS-библиотеки вроде `tls-client`. Каждый вариант даёт свой JA3, и его надо проверять отдельно.

Прокси-пул и консистентность отпечатка

Главная ошибка при работе с прокси — рассогласование слоёв. IP из Германии, User-Agent с `en-US`, часовой пояс Asia/Tokyo, а JA3 от Go-клиента. Каждый пункт по отдельности — мелочь. Вместе — приговор.

Прокси-сервис на уровне IPv6 (как делает lexic.ml с 2015-го) даёт чистые адреса, но ничего не говорит про TLS. Отпечаток формирует твой клиент, а не прокси. Поэтому связка «резидентный IPv6 + правильный TLS-стек» работает, а «резидентный IPv6 + requests» — нет.

Консистентность проверяется так: один и тот же профиль (браузер, версия, ОС) должен давать один JA3 на всех запросах сессии. Если между запросами отпечаток скачет — антифрод видит подмену клиента. Держи один клиент на сессию, не мешай `requests` и `curl_cffi` в рамках одного профиля.

Реальные кейсы

**Маркетплейс электроники, парсинг цен.** Пул из 2000 резидентных IPv6, клиент на `httpx` 0.27. Через сутки — 40% адресов в soft-block: капча на каждый десятый запрос. Причина: все 2000 IP светили один и тот же JA3 от OpenSSL 3.0, которого нет ни у одного браузера. Антифрод сгруппировал их в кластер за пару часов. Решение: переход на `curl_cffi` с эмуляцией Chrome 121, ротация профилей (Chrome 120/121/122) с привязкой к IP. Через неделю soft-block упал до 6%.

**Сервис promo-кодов, регистрация аккаунтов.** Клиент — Go с `crypto/tls`, дефолтные настройки. JA3 стабильный, но уникальный для Go-стека. Антифрод маркетплейса ловил регистрации по этому отпечатку с точностью 92% — все аккаунты с одного JA3 и разных IP банились пачкой. Причина: Go-клиент не рандомизирует extension order и не включает часть браузерных расширений (например, 17513). Решение: сборка кастомного ClientHello через `utls` с браузерным профилем. Точность детекта упала, но не до нуля — пришлось ещё выравнивать ALPN и HTTP/2-фреймы.

**Агрегатор отзывов, nginx-реверс перед пулом.** Схема: nginx терминирует TLS, дальше проксирует в пул. Проблема — nginx сам формирует исходящий ClientHello к апстриму, и его JA3 одинаков для всех клиентов. Маркетплейс видел один отпечаток с тысяч IP. Причина: TLS-терминация на своей стороне убивает клиентский отпечаток. Решение: passthrough на уровне TCP (SNI-роутинг без терминации) либо пересборка ClientHello на исходящем плече с профилем реального браузера. Первое проще и честнее.

Чего антифрод не видит

JA3 — не всевидящий. Если клиент даёт браузерный отпечаток, совпадающий по версии с User-Agent, ALPN и порядком заголовков — по TLS его не отличить от живого. Дальше решают поведенческие сигналы: тайминги, движения мыши (в headless — их отсутствие), глубина сессии, паттерны навигации.

Ещё нюанс: HTTP/2 и HTTP/3 добавляют свои отпечатки. Akamai-фингерпринт HTTP/2 (порядок и значения SETTINGS, приоритеты, window size) — отдельный слой. Chrome и Firefox дают разные h2-отпечатки, и антифрод их тоже матчит. Так что «правильный JA3» без «правильного h2» — половина работы.

И не забывай про TCP/IP-стек. TTL, window size, опции — на уровне ОС. Прокси обычно это прячет, но если пул на одной машине с дефолтным Linux-стеком — fingerprint ОС виден. Резидентные прокси с реальных устройств это нивелируют, датацентровые — нет.

Что делать, если твой трафик палят

Сначала — измерь. Сними свой JA3, сравни с браузерным эталоном для заявленного User-Agent. Инструменты: `ja3er.com`, локальный перехват, `tls-fingerprint` от FoxIO. Если расхождение есть — чини клиент, а не прокси.

Дальше — выравнивай слои. Один профиль = один браузер = один JA3 = один ALPN = один h2-отпечаток. Не смешивай. Ротируй профили медленно, не чаще раза в несколько часов на IP.

И последнее: не гоняйся за идеальной маскировкой там, где она не нужна. Публичные страницы каталога парсятся и с «палевным» JA3, если не долбить их сотней запросов в минуту. А вот регистрация и оформление заказа — места, где отпечаток решает. Экономишь усилия — распределяй их по риску эндпоинта.

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