Как антифрод TikTok связывает аккаунты по TLS JA3/JA4 при работе через разные прокси
Содержание
- Что такое JA3 и JA4
- Почему IP — не единственный маркер
- Как TikTok собирает отпечаток
- Что реально отличается между клиентами
- Почему прокси не помогает
- Как проверить свой отпечаток
- Как менять отпечаток
- Где тут место для прокси
- Реальный кейс: парсер TikTok Shop
- Кейс: мобильный TikTok через эмулятор
- Кейс: API TikTok через серверные прокси
- Что ещё смотрит TikTok
- Практические выводы
Прокси меняет IP. Это все знают. Меньше людей в курсе, что TikTok смотрит не только на IP, но и на то, как именно ваш клиент устанавливает TLS-соединение. Отпечаток TLS — это метка, которую вы тащите за собой через все прокси, даже если каждый раз берёте новый адрес. Разберём, как это работает и почему смена прокси не спасает от связывания.
Что такое JA3 и JA4
JA3 — это хеш от параметров ClientHello. В 2017 году Salesforce выкатил концепцию: берём поля из TLS-рукопожатия, склеиваем в строку, считаем MD5. Получаем 32-символьный отпечаток, стабильный для конкретного клиента.
Что входит в строку:
- версия TLS (например, 771 — это TLS 1.2)
- список cipher suites в порядке следования
- список extensions
- список elliptic curves
- формат точек эллиптических кривых
JA4 — следующее поколение, появился в 2023. Он умнее: учитывает, что клиент может поддерживать QUIC, разделяет отпечатки по типу транспорта, не так сильно зависит от порядка некоторых полей. Формат выглядит как `t13d1516h2_8daaf6152771_b186095e22b6` — три части через подчёркивание.
TikTok парсит оба. Если у вас старый клиент или кастомная сборка curl — вы попадёте в JA3. Если свежий Chrome — попадёте в JA4-категорию, но JA3 всё равно посчитают для совместимости.
Почему IP — не единственный маркер
Классическая логика антифрода: если два аккаунта заходят с одного IP — они связаны. Прокси ломает эту связь. Но остаётся куча других сигналов, и TLS — один из самых устойчивых.
Пример: у вас десять аккаунтов, десять прокси. Каждый раз новый IP, новая геолокация, чистые куки. Но все десять заходят через одну и ту же сборку Chromium с одинаковым набором cipher suites. JA3 у всех идентичен. Антифрод складывает это в граф: узел «JA3 = abc123...» соединён с десятью аккаунтами. Всё, вы в одной группе.
Причём это работает даже если IP разные, часовые пояса разные, языки разные. Отпечаток TLS не зависит от прокси. Прокси работает на уровне TCP, а JA3 формируется выше — на уровне TLS-рукопожатия. Прокси видит только зашифрованный поток и не может его подменить, если не делает MITM.
Как TikTok собирает отпечаток
TikTok использует несколько слоёв сбора. Первый — на уровне CDN и edge-серверов. Когда ваш клиент приходит на `*.tiktokcdn.com` или `*.tiktokv.com`, TLS-терминация происходит на балансировщике. Он логирует ClientHello целиком, включая JA3 и JA4.
Второй слой — внутри приложения. Мобильный клиент TikTok отправляет на сервер кучу телеметрии: `device_id`, `iid`, `openudid`, версию WebView, набор поддерживаемых шифров на уровне приложения (там свой TLS-стек). Это уже не JA3 в чистом виде, но сервер может сопоставить: «клиент заявил Chrome 120, а JA3 у него от Chrome 118». Расхождение — красный флаг.
Третий слой — поведенческий. Но он нас сейчас не интересует.
Собранные отпечатки складываются в профиль. Если с одного JA3 приходит 50 регистраций за сутки — это фрод-паттерн. Если с одного JA3 приходит один аккаунт годами — норма.
Что реально отличается между клиентами
Разные клиенты дают разные JA3. Вот реальные значения для популярных стеков (упрощённо, только для иллюстрации):
| Клиент | JA3 (MD5) | JA4 |
|---|---|---|
| Chrome 120 (Win) | `cd08e31494f9531f560d64c695473da9` | `t13d1516h2_8daaf6152771_b186095e22b6` |
| Firefox 121 | `579ccef312d18482fc42e2b822ca2430` | `t13d1715h2_5b57614c22b0_3cbfd9057e0d` |
| curl 8.5 (OpenSSL 3.0) | `3b5074b1b5d032e5620f69f9f700ff0e` | `t13d3112h2_e8f1e7e78f70_9b8f3e9d6c3a` |
| Python requests 2.31 | `3b5074b1b5d032e5620f69f9f700ff0e` | `t13d3112h2_e8f1e7e78f70_9b8f3e9d6c3a` |
| Go net/http 1.21 | `9e10a8b1e9c3b4a3c4e5f6a7b8c9d0e1` | `t13d2210h2_...` |
Заметьте: curl и Python requests дают одинаковый JA3, потому что оба используют OpenSSL с дефолтными настройками. Это классическая ловушка. Гоняете парсер на Python через ротацию прокси — а антифрод видит один и тот же отпечаток на всех запросах.
Почему прокси не помогает
Прокси на уровне SOCKS5 или HTTP CONNECT просто туннелирует TCP-поток. TLS-рукопожатие идёт между вашим клиентом и сервером TikTok напрямую. Прокси видит только байты, но не может их изменить — иначе сломается шифрование.
Единственный способ подменить JA3 через прокси — MITM-прокси с собственным сертификатом. Тогда прокси терминирует TLS, читает ClientHello, подменяет его и устанавливает новое соединение с сервером. Но для этого нужен доверенный сертификат на клиенте. Для мобильного TikTok это невозможно — приложение пиннит сертификаты.
Итог: какой бы прокси вы ни взяли, JA3 вашего клиента уедет на сервер как есть.
Как проверить свой отпечаток
Самый простой способ — отправить запрос на сервис, который показывает ваш JA3/JA4. Например, `tls.peet.ws` или `ja3er.com`.
```bash
curl -s https://tls.peet.ws/api/all | jq '.tls.ja3, .tls.ja4'
```
Для Python:
```python
import requests
r = requests.get("https://tls.peet.ws/api/all")
data = r.json()
print("JA3:", data["tls"]["ja3"])
print("JA3 hash:", data["tls"]["ja3_hash"])
print("JA4:", data["tls"]["ja4"])
print("Akamai fingerprint:", data["http2"]["akamai_fingerprint"])
```
Запустите это через разные прокси. Отпечаток не изменится. Запустите с разных машин с разными версиями Python — тоже, скорее всего, не изменится, потому что OpenSSL один и тот же.
Как менять отпечаток
Есть несколько путей.
Первый — использовать curl-impersonate или его форки. Это патченный curl, который воспроизводит TLS-рукопожатие Chrome или Firefox один в один. JA3 совпадает с реальным браузером.
```bash
curl_chrome120 -s https://tls.peet.ws/api/all | jq '.tls.ja4'
```
Второй — библиотеки вроде `curl_cffi` для Python. Она оборачивает curl-impersonate и даёт питонический API.
```python
from curl_cffi import requests
r = requests.get(
"https://tls.peet.ws/api/all",
impersonate="chrome120",
proxies={"https": "socks5://user:pass@proxy.example:1080"}
)
print(r.json()["tls"]["ja4"])
```
Третий — собственный TLS-стек на Go или Rust с настраиваемым ClientHello. Это уже для параноиков, но даёт полный контроль.
Четвёртый — использовать реальные браузеры через Playwright или Puppeteer. Тогда JA3 будет настоящим, но вы получите все остальные отпечатки браузера тоже — canvas, WebGL, шрифты. Что тоже неплохо, если задача — выглядеть как живой пользователь.
Где тут место для прокси
Прокси нужны для смены IP. Это базовая гигиена. Но если вы гоняете один и тот же Python-скрипт через сто прокси — антифрод свяжет все сто сессий по JA3 за пять минут.
Правильная схема: разные клиенты с разными отпечатками + ротация прокси. Тогда каждый запрос выглядит как отдельный пользователь. Сервисы вроде lexic.ml дают IPv6-пулы, где каждый адрес — отдельная /128, и это хорошо ложится на схему «один отпечаток — один IP — один аккаунт».
Но IPv6 сам по себе не спасёт. Если у вас один JA3 на весь пул — связывание всё равно произойдёт. Отпечаток и IP должны меняться вместе.
Реальный кейс: парсер TikTok Shop
Команда делала мониторинг цен на TikTok Shop. Сто аккаунтов, сто резидентских прокси, ротация по кругу. Через три дня — бан 80% аккаунтов.
Причина: все запросы шли через `requests` с дефолтным OpenSSL. JA3 одинаковый. Плюс HTTP/2 fingerprint тоже одинаковый — а TikTok логирует и его. Akamai fingerprint (порядок заголовков, приоритеты потоков) совпадал до байта.
Решение: переписали на `curl_cffi` с `impersonate="chrome120"`. Отпечатки стали разные — рандомизировали между Chrome 118, 119, 120, Firefox 120, Safari 17. Плюс добавили рандомные задержки между запросами и разные User-Agent, синхронизированные с JA3. Баны упали до 5% в неделю.
Кейс: мобильный TikTok через эмулятор
Другой пример. Ребята гоняли мобильный TikTok через эмуляторы Android. Каждый эмулятор — свежий `device_id`, свежий прокси. Но TLS-стек внутри Android WebView одинаковый у всех эмуляторов одной версии.
TikTok видел: сто аккаунтов, сто device_id, но один JA4. Плюс один и тот же `iid` в телеметрии (потому что эмулятор клонировали). Связывание произошло мгновенно. Плюс бан по железу — эмуляторы палились по другим маркерам.
Решение: разные версии Android на разных эмуляторах, разные версии WebView, рандомизация `iid`, плюс прокси с IPv6. JA4 разошёлся. Но полностью проблему не убрало — мобильный антифрод TikTok копает глубже, чем TLS.
Кейс: API TikTok через серверные прокси
Тут интереснее. Компания дёргала TikTok API для аналитики. Запросы шли с серверных IP, через датацентровые прокси. IP чистые, но все из одной подсети /24.
JA3 одинаковый — Go net/http. JA4 тоже. Плюс HTTP/2-фреймы идентичные. TikTok сгруппировал все запросы в один кластер и прикрутил rate limit на весь кластер.
Решение: переписали клиент на Rust с `reqwest` и кастомным TLS-стеком (`rustls` с настраиваемым ClientHello). Плюс раскидали по разным /24 и /48. Плюс разные версии TLS — где-то 1.2, где-то 1.3. Rate limit перестал срабатывать на весь пул.
Что ещё смотрит TikTok
JA3/JA4 — не единственное. Есть HTTP/2 fingerprint (набор SETTINGS-фреймов, порядок заголовков, WINDOW_UPDATE). Есть TCP fingerprint (TTL, размер окна, опции). Есть DNS-резолвер, к которому обращается клиент. Есть timing — задержки между пакетами в рукопожатии.
Всё это складывается в один большой профиль. JA3 — просто самый удобный маркер, потому что он стабильный и легко считается.
Если хотите реально развести аккаунты — меняйте весь стек: TLS, HTTP/2, TCP, DNS, timing. Прокси меняет только IP. Это necessary, но не sufficient.
Практические выводы
Первое. Никогда не гоняйте один и тот же клиент через ротацию прокси. Отпечаток уедет один и тот же.
Второе. Используйте инструменты с подменой отпечатка: curl-impersonate, curl_cffi, Playwright. Они дают реалистичные JA3/JA4.
Третье. Синхронизируйте User-Agent с отпечатком. Если JA3 от Chrome 120, а UA от Chrome 118 — это палево.
Четвёртое. Меняйте отпечатки вместе с IP. Один отпечаток — один IP — один аккаунт. Иначе граф свяжет всё в кучу.
Пятое. Проверяйте себя. `tls.peet.ws` покажет JA3, JA4, Akamai fingerprint. Прогоните через него свой стек и посмотрите, на что вы похожи.
TikTok не дураки. Они собирают всё, что можно собрать. Прокси — это только первый слой. Кто останавливается на нём, тот попадает в бан через неделю и не понимает почему.