Как антифрод TikTok палит мультиаккаунты по TLS-отпечатку при смене IPv6
Содержание
- Что вообще такое TLS-отпечаток
- Почему IPv6-подсеть ничего не решает
- Как выглядит связка сигналов
- Пример: как выглядит утечка в логах
- HTTP/2 fingerprint — второй слой
- TCP/IP fingerprint и почему VPS выдают себя
- Кейс: ферма аккаунтов на Hetzner
- Можно ли подделать JA3
- Что реально собирает TikTok на edge
- Короткий чек-лист самопроверки
Смена IPv6-подсети — классический приём в арсенале мультиаккаунтеров. Логика простая: если каждый аккаунт ходит со своего /64, то по IP их не связать. Проблема в том, что TikTok уже года три как не полагается на IP как на основной сигнал. Антифрод-система строит отпечаток клиента на транспортном уровне, и этот отпечаток переезжает между подсетями без потерь. Разберём, что именно утекает и почему.
Что вообще такое TLS-отпечаток
При установке TLS-соединения клиент отправляет ClientHello — первый пакет хендшейка. В нём лежит набор расширений, список поддерживаемых шифров (cipher suites), группы для ECDHE, версии протокола, порядок расширений и кучу мелочей вроде ALPN и поддерживаемых сигнатур. Этот набор уникален для связки «библиотека + версия + конфигурация». Chrome 120 и Chrome 121 отправят разные ClientHello. Curl с OpenSSL 3.0 — третий вариант. Python requests с urllib3 — четвёртый, и он будет стабильно узнаваем.
JA3 — самый известный способ хешировать эту информацию. Берётся версия TLS, список cipher suites, список расширений, список эллиптических кривых и форматы точек. Всё склеивается через запятую, потом считается MD5. Получается 32-символьный хеш. JA3S — то же самое, но для ServerHello. Есть ещё JA4, JA4+, JA4H — более свежие и устойчивые к рандомизации варианты, которые учитывают порядок и наличие GREASE-значений.
Ключевой момент: JA3 не зависит от IP. Он вообще не зависит от сетевого уровня. Хоть с /64, хоть с /48, хоть через NAT — отпечаток остаётся тем же, пока не поменялась TLS-библиотека и её конфиг.
Почему IPv6-подсеть ничего не решает
IPv6 даёт огромное адресное пространство, и это создаёт иллюзию анонимности. Провайдер выдаёт /56 или /48, внутри — миллиарды /64. Мультиаккаунтер раскидывает аккаунты по разным /64 и считает, что чист. Но антифрод TikTok смотрит не на конкретный адрес, а на совокупность сигналов. И /64 — это лишь один из десятков параметров.
Есть нюанс с префиксами. Если все /64 принадлежат одному /48, это уже сигнал: антифрод группирует адреса по префиксам и видит, что сотня «разных» клиентов сидит в одной выделенной подсети хостера. Датацентровые диапазоны (AS14061 у DigitalOcean, AS16509 у AWS, AS24940 у Hetzner) помечены в базах как hosting — и это само по себе понижает доверие. Смена /64 внутри одного /48 не обманывает ничего.
Плюс IPv6-адрес привязан к устройству через SLAAC. Если MAC-адрес или privacy-расширения генерируют предсказуемый интерфейсный идентификатор, это тоже учитывается. Но главное — TLS.
Как выглядит связка сигналов
Антифрод не принимает решение по одному признаку. Он собирает вектор и сравнивает с кластерами известных нарушителей. Вот что реально уходит на сервер TikTok при обычном запросе к API или загрузке ленты:
| Сигнал | Источник | Стабильность при смене /64 |
|---|---|---|
| JA3 / JA4 | TLS ClientHello | не меняется |
| HTTP/2 fingerprint (SETTINGS, WINDOW_UPDATE, priority) | HTTP/2 preface | не меняется |
| Порядок заголовков HTTP | запрос | не меняется |
| User-Agent + sec-ch-ua | заголовки | не меняется, если не подделать |
| TLS-расширения (ALPN, supported_groups) | ClientHello | не меняется |
| TCP/IP fingerprint (TTL, window size, MSS) | ядро ОС | может меняться при смене VPS |
| IPv6-префикс /48 | сетевой уровень | меняется только при смене провайдера |
| Время отклика до edge-ноды | RTT | меняется незначительно |
Видно, что сетевой уровень — самый слабый сигнал из списка. Его проще всего подделать, поэтому вес у него минимальный.
Пример: как выглядит утечка в логах
Допустим, у нас есть питон-скрипт, который логинится в TikTok через requests. Запускаем два аккаунта с разных /64 — и оба светят одинаковый JA3.
```python
import requests
proxies = {
"https": "http://[2a01:4f8:1c1c:abcd::1]:3128"
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/121.0.0.0 Safari/537.36"
}
r = requests.get("https://www.tiktok.com/api/user/detail/",
headers=headers, proxies=proxies, timeout=15)
print(r.status_code)
```
С точки зрения IP — два разных адреса. С точки зрения TLS — один и тот же клиент: OpenSSL 3.0.x из состава Python 3.11, JA3 вроде `3b5074b1b5d032e5620f69f9f700ff0e`. Chrome с таким JA3 никогда не придёт. Антифрод видит несоответствие: User-Agent врёт про Chrome, а хендшейк — от Python. Это называется TLS mismatch, и он сам по себе повод для challenge.
Проверить свой отпечаток можно так:
```bash
curl -s https://tls.peet.ws/api/all \
--resolve tls.peet.ws:443:2606:4700::6810:6f0a \
| jq '{ja3: .tls.ja3_hash, ja4: .tls.ja4, akamai: .http2.akamai_fingerprint}'
```
Если JA3 совпадает у десяти аккаунтов с разных /64 — они связываются в один кластер за один проход по логам.
HTTP/2 fingerprint — второй слой
Даже если TLS-стек совпал (например, всё через один и тот же curl), остаётся HTTP/2. Клиент отправляет preface, за ним SETTINGS-фрейм с параметрами: MAX_CONCURRENT_STREAMS, INITIAL_WINDOW_SIZE, MAX_FRAME_SIZE. У curl, Chrome, Go-http2 эти значения разные. Плюс порядок и значения WINDOW_UPDATE, приоритеты потоков, наличие PRIORITY-фреймов.
Akamai придумал хешировать это в fingerprint вида `1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p`. Chrome 121 выдаёт один паттерн, curl 8.x — другой, Go 1.21 — третий. Антифрод TikTok сверяет JA3 + HTTP/2 fingerprint + User-Agent. Все три должны указывать на один и тот же браузер и версию. Любое расхождение — красный флаг.
TCP/IP fingerprint и почему VPS выдают себя
На уровне ядра ОС тоже есть отпечаток. TTL по умолчанию (64 у Linux, 128 у Windows, 255 у сетевого оборудования), размер окна TCP, опции (timestamps, SACK, window scaling), MSS. Инструмент p0f строит по этому отпечатку профиль ОС. Если User-Agent говорит «Windows 10», а TTL=64 и window size=29200 — это Linux. Классическая нестыковка.
Когда аккаунты крутятся на VPS с Linux, а маскируются под Windows-браузер, антифрод это видит. Смена IPv6-подсети тут не помогает вообще: ядро-то то же самое, и TCP-отпечаток стабилен. Хотите эмулировать Windows — придётся либо запускать реальную Windows-машину, либо использовать прокси, который терминирует TCP и пересобирает соединение с нужными параметрами. Обычный SOCKS5 так не умеет — он прозрачен на уровне TCP.
Кейс: ферма аккаунтов на Hetzner
Пример из практики. Сборка из 40 аккаунтов TikTok жила на трёх VPS в Hetzner (AS24940), каждый со своим /64. Прокси-слой — squid на каждом сервере, клиент — антидетект-браузер на базе Chromium с подменой User-Agent и canvas. Первые две недели всё работало. На третьей волне банов прилетело 37 из 40 аккаунтов за сутки.
Разбор показал: все 40 аккаунтов имели одинаковый JA3 (сборка Chromium одна и та же, патчи не меняли порядок расширений), одинаковый HTTP/2 fingerprint, одинаковый набор TCP-опций (один и тот же Debian 12 с дефолтным sysctl). Различались только IPv6-адреса и cookies. Антифрод сгруппировал их по TLS+HTTP/2 и увидел, что 40 «разных пользователей» сидят на трёх ASN с идентичными отпечатками стека. Плюс RTT до edge-нод TikTok из Франкфурта был подозрительно ровным — 8-11 мс, что для реальных пользователей из разных стран редкость.
Решение: разнесли по разным ASN (не только хостинг, но и residential-прокси в разных странах), для каждого профиля — своя сборка браузера с рандомизированным порядком TLS-расширений (JA4 стал уникальным), разные версии ядра и разные sysctl-настройки TCP. Плюс добавили jitter в RTT через tc netem. Выживаемость выросла, но это уже не «смена подсети», а полноценная эмуляция парка устройств.
Можно ли подделать JA3
Технически да, но это не тривиально. Порядок расширений в ClientHello задаётся библиотекой. Чтобы его поменять, нужно либо патчить библиотеку (uTLS в Go, curl-impersonate, tls-client на Go), либо использовать инструменты вроде `curl_cffi` в Python, которые эмулируют конкретные браузерные отпечатки.
```python
from curl_cffi import requests
r = requests.get(
"https://www.tiktok.com/api/user/detail/",
impersonate="chrome120",
proxies={"https": "http://[2a01:4f8:1c1c:abcd::1]:3128"},
timeout=15,
)
print(r.status_code, r.http_version)
```
`curl_cffi` подменяет TLS-стек на BoringSSL с настройками Chrome 120, включая порядок расширений, GREASE-значения и ALPN. JA3 становится похож на хромий. Но дьявол в деталях: HTTP/2 fingerprint тоже надо подогнать, иначе снова mismatch. И TCP-отпечаток остаётся линуксовым, если не сидеть на Windows-прокси.
Плюс есть JA4, который смотрит не только на состав, но и на порядок, и на наличие определённых расширений. Подделать всё сразу — задача на уровне полноценного браузерного движка. Именно поэтому антидетект-браузеры на базе реального Chromium работают лучше, чем самописные эмуляторы.
Что реально собирает TikTok на edge
Edge-ноды TikTok (они сидят за собственным CDN, частично на Akamai и Fastly) логируют всё, что приходит на L4-L7. При логине и регистрации включается усиленный сбор: TLS-параметры, HTTP/2-фреймы, полный набор заголовков с порядком, тайминги запросов, поведенческие паттерны (скорость скролла, интервалы между тапами). Всё это уходит в антифрод-движок, который строит граф связей.
Два аккаунта связываются, если у них совпадает достаточно сигналов. JA3 — сильный сигнал: совпадение редкого отпечатка (например, от кастомного клиента) почти гарантированно означает одного оператора. Совпадение распространённого (Chrome 121) — слабее, но в комбинации с одинаковым HTTP/2 fingerprint и одинаковым набором заголовков даёт высокий скор.
IPv6-адрес в этом графе — лишь одно ребро из многих. Смена /64 убирает одно ребро, но оставляет десяток других. Хотите реально развести аккаунты — придётся разводить весь стек: TLS-библиотеку, версию, порядок расширений, HTTP/2-параметры, TCP-опции, тайминги. И даже тогда остаются поведенческие сигналы, которые сетевыми средствами не подделать.
Если нужен чистый сетевой слой без привязки к одному /48 — IPv6-прокси с раздачей адресов из разных префиксов решают часть задачи, но не отменяют необходимости приводить TLS и HTTP/2 в порядок. Инфраструктура вроде lexic.ml как раз про сетевую часть: разные /64 из независимых пулов, стабильные маршруты. Отпечаток браузера — уже ваша зона ответственности.
Короткий чек-лист самопроверки
Прогоните свой стек через пару тестов, прежде чем заливать аккаунты. Проверьте JA3 и JA4 на tls.peet.ws или browserleaks. Сравните с эталонным Chrome той же версии, под которую маскируетесь. Гляньте HTTP/2 fingerprint — он должен соответствовать тому же браузеру. Проверьте TCP-отпечаток через p0f или удалённый сервис: TTL, window size, MSS должны совпадать с заявленной ОС.
Если хотя бы один слой расходится — антифрод это увидит. Не сегодня, так через неделю, когда наберётся статистика. Смена IPv6-подсети даёт отсрочку, но не решает проблему. Реальная изоляция аккаунтов — это изоляция всего стекла, от ядра до поведения, а не только адреса.