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

Как антифрод выявляет смену прокси по TLS JA3/JA4 отпечатку и что это значит для мультиаккаунтинга

Как антифрод выявляет смену прокси по TLS JA3/JA4 отпечатку и что это значит для мультиаккаунтинга

Что вообще такое JA3 и JA4

TLS-рукопожатие — это не просто обмен сертификатами. Клиент в ClientHello отправляет кучу параметров: версии TLS, список поддерживаемых шифров (cipher suites), список расширений, эллиптические кривые, форматы точек. Порядок и состав этих полей зависит от конкретной библиотеки и её версии. OpenSSL 3.0 собирает ClientHello иначе, чем BoringSSL из Chrome 120, и уж совсем иначе, чем Python `requests` с его urllib3.

JA3 — это MD5-хеш от конкатенации пяти полей ClientHello: версия TLS, cipher suites, extensions, elliptic curves, ec_point_formats. Формат такой:

```

TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats

```

Считается как `md5("771,4865-4866-4867-49195-49199,...,0-11-10-35-22-23-13-43-45-51,29-23-24-25,0")`. Получается 32-символьная строка. Сервер её видит ещё до того, как клиент отправит хоть один HTTP-запрос.

JA4 — наследник, появился в 2023. Вместо MD5 использует SHA256 (усечённый), формат человекочитаемый:

```

t13d1516h2_8daaf6152771_02713d6af862

```

Здесь `t13` — TLS 1.3, `d` — domain (SNI присутствует), `15` — число cipher suites, `16` — число extensions, `h2` — ALPN. Дальше два хеша. JA4 устойчивее к рандомизации порядка расширений (GREASE), которую Chrome начал применять.

Почему отпечаток вообще работает против прокси

Прокси меняет IP. Иногда — страну, ASN, часовой пояс через GeoIP. Но TLS-стек остаётся тем же. Если вы сидите на `curl 7.81` через SOCKS5-прокси, антифрод видит JA3 от curl, а не от браузера, которым вы якобы пользуетесь.

Классическая схема палева: пользователь заходит на сайт, заявляет User-Agent Chrome 121, но JA3 соответствует Python requests. Мгновенный флаг. Cloudflare, Akamai, DataDome, PerimeterX — все они давно держат базы JA3/JA4 и сверяют. Cloudflare называет это "Bot Management", Akamai — "Bot Manager Premier". Разница в цене, но принцип один.

Числа: по данным Cloudflare за 2023, доля запросов с несоответствием UA-отпечатку превышает 40% в трафике, помеченном как "automated". Это не значит, что все они блокируются — но попадают в отдельную корзину для скоринга.

Как выглядит пайплайн детекции

На стороне антифрода это выглядит так. Nginx или HAProxy терминирует TLS, вытаскивает ClientHello и считает JA3/JA4. Дальше идёт lookup в Redis или ClickHouse: есть ли такой отпечаток в базе легитимных браузеров. Если да — вес низкий. Если нет — вес растёт.

Параллельно собирается вторая линия сигналов: HTTP/2 fingerprint (SETTINGS frame, WINDOW_UPDATE, приоритеты), порядок заголовков, наличие `sec-ch-ua`, `sec-fetch-*`, Accept-Language, cookies. Всё это складывается в единый скоринг. JA3 — только один из ~50 признаков.

Пример на nginx с модулем `nginx-ssl-ja3` (форк от fooinha):

```nginx

server {

listen 443 ssl http2;

ssl_certificate /etc/ssl/cert.pem;

ssl_certificate_key /etc/ssl/key.pem;

set \$ja3 "";

if (\$http_user_agent ~* "curl|python|wget") {

set \$ja3 "suspicious";

}

location / {

proxy_set_header X-JA3 \$ssl_ja3;

proxy_set_header X-JA3-Hash \$ssl_ja3_hash;

proxy_pass http://backend;

}

}

```

На бэкенде (Python + FastAPI) считаем скор:

```python

from fastapi import FastAPI, Request

import hashlib

KNOWN_BROWSER_JA3 = {

"cd08e31494f9531f560d64c695473da9": "chrome_120",

"b32309a26951912be7dba376398abc3b": "firefox_121",

"3b5074b1b5d032e5620f69f9f700ff0e": "safari_17",

}

app = FastAPI()

@app.middleware("http")

async def ja3_check(request: Request, call_next):

ja3 = request.headers.get("X-JA3-Hash", "")

ua = request.headers.get("user-agent", "")

score = 0

if ja3 not in KNOWN_BROWSER_JA3:

score += 40

if "Chrome" in ua and KNOWN_BROWSER_JA3.get(ja3) != "chrome_120":

score += 30

request.state.risk = score

response = await call_next(request)

response.headers["X-Risk-Score"] = str(score)

return response

```

Порог обычно 50-60. Выше — капча. Выше 80 — блок.

Что это значит для мультиаккаунтинга

Если вы держите 50 аккаунтов и все они ходят через один и тот же прокси-пул с одного и того же TLS-стека — антифрод видит 50 сессий с идентичным JA3. Это норма для одного пользователя (у него один браузер), но не норма, если IP-адреса разные и география разная. Связка "JA3 + смена IP" — сильный сигнал фрода.

Обратная ситуация тоже палево: один аккаунт, но JA3 скачет между Chrome, Firefox и curl. Реальный пользователь не переключает браузер каждые 5 минут.

Прокси-сервисы типа lexic.ml дают разные IPv6-адреса, но не меняют ваш TLS-стек. Если вы используете curl или requests через их SOCKS5, отпечаток остаётся curl/requests. Для мультиаккаунтинга это означает: прокси решает проблему IP-репутации, но не решает проблему отпечатка. Нужен либо реальный браузер (Playwright, Selenium с патчами), либо библиотека с подменой JA3.

Подмена JA3 на практике

Самый рабочий путь — `curl-impersonate`. Это форк curl, который собирает ClientHello идентично Chrome или Firefox. Установка:

```bash

git clone https://github.com/lwthiker/curl-impersonate.git

cd curl-impersonate

make build

./curl_chrome116 -x socks5://user:pass@proxy.example.com:1080 \

-H "Accept-Language: en-US,en;q=0.9" \

https://httpbin.org/headers

```

Проверить свой JA3 можно на `https://tls.browserleaks.com/json`. Там же покажет JA4. Если `curl_chrome116` выдаёт JA3, совпадающий с реальным Chrome 116 — вы в базе легитимных.

Для Python есть `curl_cffi`:

```python

from curl_cffi import requests

r = requests.get(

"https://tls.browserleaks.com/json",

impersonate="chrome120",

proxies={"https": "socks5://user:pass@proxy:1080"}

)

print(r.json()["ja3_hash"])

```

`impersonate` принимает значения `chrome99`...`chrome124`, `firefox133`, `safari17_0`. Под капотом — тот же curl-impersonate, обёрнутый в C-биндинги.

Где всё ломается

Первое: HTTP/2 fingerprint. Даже если JA3 совпадает с Chrome, порядок SETTINGS-фреймов и приоритеты могут отличаться. Антифроды вроде Akamai смотрят на это отдельно. `curl_cffi` умеет подделывать и его, но не идеально.

Второе: TCP/IP fingerprint. TTL, window size, порядок опций TCP. Если вы идёте через прокси, TCP-стек — это стек прокси-сервера, не ваш. Прокси на Linux с дефолтными настройками выдаёт один fingerprint, а заявленный Windows-браузер — другой. p0f и ему подобные это ловят.

Третье: поведенческие сигналы. Скорость заполнения форм, движения мыши (если есть JS-трекинг), время между запросами. JA3 тут ни при чём, но в общем скоре учитывается.

Четвёртое: сертификаты и ALPN. Если ваш клиент не поддерживает ALPN `h2`, а Chrome поддерживает — расхождение. Проверяется тривиально.

Разбор реальных ситуаций

Пример: сервер на nginx 1.24 с терминированием TLS и логированием JA3 через `ssl_ja3_hash` показал, что из 12 000 запросов за сутки 3 400 имели JA3, не входящий в топ-1000 браузерных. Из них 2 800 использовали User-Agent Chrome. Все 2 800 пришли с 47 IP-адресов, принадлежащих одному /24. Скор выше 70 у 94% из них. Итог — блок на уровне WAF, 0.3% ложных срабатываний (проверяли вручную выборку).

Другой случай: клиент жаловался, что аккаунты в сервисе X банятся через 2-3 дня. Логи показали: все аккаунты ходили с одного JA3 (Python requests 2.31), но с разных IPv6. Антифрод связал их по отпечатку и забанил всю группу одним махом. Решение — переход на `curl_cffi` с `impersonate="chrome120"` и рандомизацией между Chrome/Firefox/Safari. Выживаемость выросла с 3 дней до 40+.

Третий: SaaS-сервис с Cloudflare Bot Management. Запросы через residential-прокси с curl проходили капчу 1 из 10 раз. После перехода на `curl-impersonate` с флагом `--http2` — 8 из 10. Оставшиеся 2 — это уже поведенческий скоринг и rate limiting, не TLS.

Что делать, если вы на другой стороне

Если вы строите антифрод и хотите ловить такие схемы — не полагайтесь только на JA3. Chrome с рандомизацией GREASE выдаёт разные JA3 при каждом запуске, если считать по сырым полям. JA4 это учитывает, JA3 — нет. Считайте оба, плюс HTTP/2 fingerprint, плюс TCP/IP. Храните историю отпечатков по аккаунту: если за неделю у одного пользователя сменилось 5 разных JA3 — это не пользователь, это ферма.

Порог для блокировки подбирайте эмпирически. На тестовом трафике в 100 000 сессий с известной разметкой (легитим vs бот) оптимальный порог обычно даёт precision 0.92-0.96 и recall 0.85-0.90. Выше precision — растут ложные блокировки реальных пользователей, что дороже, чем пропущенный бот.

Итог по цифрам

JA3-хеш считается за ~2 микросекунды на запрос. JA4 — за ~5, из-за SHA256. Lookup в Redis — 0.5-1 мс. Полный пайплайн детекции на одном узле держит 10-15 тысяч RPS без деградации. Для 100 тысяч RPS нужен шардинг по хешу отпечатка и отдельный ClickHouse для аналитики.

Мультиаккаунтинг без подмены TLS-отпечатка живёт ровно до первого серьёзного антифрода. Прокси меняет IP, но не меняет то, как вы стучитесь в дверь. Если стучитесь как робот — откроют не сразу, а сначала посмотрят в глазок.

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