TikTok через IPv6 прокси: как работает детект мультиаккаунтов по TLS-сессиям
Содержание
- Что именно видит сервер TikTok на рукопожатии
- Почему IPv6 прокси сам по себе не спасает
- Как выглядит JA4 в реальности
- Кейс: двадцать аккаунтов на одном /64
- Почему curl_cffi и подобное — не панацея
- Ротация IPv6 внутри сессии и TLS session tickets
- Что видит TikTok на уровне HTTP/2
- Как выглядит нормальная архитектура фермы
- Кейс: подмена JA3 без подмены HTTP/2
- Кейс: DNS-утечка через IPv6
- Кейс: session resumption через разные /64
- Что делать, если аккаунты уже начали падать
- Итог
TikTok ловит мультиаккаунты не по IP. Точнее, не только по IP. За последние три года платформа перестроила детект: теперь основной сигнал — не адрес, а отпечаток TLS-сессии и её поведение во времени. Прокси решает задачу сетевого уровня, но если TLS-стек у вас один на двадцать аккаунтов — вы уже спалились. Разбираем, как это работает изнутри и что делать.
Что именно видит сервер TikTok на рукопожатии
Когда клиент открывает соединение к `www.tiktok.com:443`, до первого байта HTTP происходит ClientHello. В нём — набор шифров, расширения, эллиптические кривые, порядок групп, ALPN, версии TLS, размеры записей. Всё это вместе называется JA3 (и его наследник JA4). Плюс — GREASE-значения, SNI, поддержка ECH, набор сигнатур пост-квантовых групп (X25519Kyber768).
Сервер собирает эти параметры в строку, хеширует и сравнивает с тем, что ожидает увидеть от «настоящего» мобильного клиента TikTok. Реальный Android-клиент на OkHttp 4.x даёт один хеш. Chrome на десктопе — другой. Python-requests с дефолтным OpenSSL — третий, и он вообще не похож ни на что живое. Если двадцать аккаунтов приходят с одинаковым JA4, но заявляют разные устройства в User-Agent — это красный флаг первого уровня.
Дальше — больше. TLS-сессия имеет состояние: session tickets, resumption, порядок расширений при повторном хендшейке. TikTok смотрит на то, как клиент возобновляет сессии. Браузер с одним профилем возобновляет их предсказуемо. Скрипт на requests — либо не возобновляет вообще, либо делает это хаотично.
Почему IPv6 прокси сам по себе не спасает
IPv6 даёт огромный пул адресов — /64 на клиента, /48 на провайдера. Кажется, что можно выдавать по адресу на аккаунт и жить спокойно. На практике TikTok давно перестал считать IPv6-адрес уникальным идентификатором. Провайдеры мобильной связи ротируют адреса внутри /64 каждые несколько минут. Значит, привязка «один аккаунт = один IPv6» технически бессмысленна для платформы.
Более того: если двадцать аккаунтов приходят с одного /64, но с разными адресами, TikTok всё равно видит их как одну подсеть. Поведенческие модели это учитывают. Прокси-провайдеры, которые продают «чистые IPv6», часто дают адреса из одного пула с одинаковым ASN — и это тоже сигнал.
Прокси нужен, чтобы развести сетевой уровень. Но если TLS-стек общий — сетевой уровень уже не важен. Аккаунты склеятся по отпечатку.
Как выглядит JA4 в реальности
JA4 — это строка формата `t13d1516h2_8daaf6152771_02713d6af862`. Первая часть — версия TLS, число шифров, число расширений, ALPN. Вторая — хеш отсортированных шифров. Третья — хеш расширений и сигнатур. Сравним типичные значения:
| Клиент | JA4 (сокращённо) | Примечание |
|---|---|---|
| Chrome 124 (Win) | `t13d1516h2_8daaf6152771_...` | Стабилен, узнаваем |
| OkHttp 4.12 (Android) | `t13d1715h2_5b57614c22b0_...` | Мобильный TikTok |
| Python requests 2.31 | `t13d1717h2_5b57614c22b0_...` | Совпадает с curl частично |
| curl 8.5 (OpenSSL 3.2) | `t13d1717h2_5b57614c22b0_...` | Тот же хеш, что requests |
| Go net/http 1.22 | `t13d1811h2_...` | Уникальный, редко встречается |
Видите проблему? `requests` и `curl` на одной машине дают один и тот же JA4, потому что оба используют системный OpenSSL. Если вы гоняете скрипты через curl и через Python — сервер видит один клиент. Двадцать аккаунтов, десять из которых на curl, десять на requests — все двадцать с одинаковым отпечатком.
Кейс: двадцать аккаунтов на одном /64
Разработчик запустил ферму из двадцати аккаунтов TikTok. Каждому выдал отдельный IPv6 из /64 через SOCKS5. User-Agent рандомизировал — Android, iOS, разные версии. Через четыре дня — бан десяти аккаунтов из двадцати.
Причина: все двадцать ходили через один Python-скрипт на requests 2.31. JA4 совпадал у всех. TikTok сгруппировал их по отпечатку TLS и увидел: «двадцать устройств с разными UA, но идентичным TLS-стеком, из одной /64». Классический паттерн фермы.
Решение: развести TLS-стеки. Часть аккаунтов — через curl_cffi с эмуляцией Chrome, часть — через реальный headless-браузер с разными профилями, часть — через мобильные эмуляторы с OkHttp. Плюс — распределить по разным /48. Через две недели — ноль банов на тех же аккаунтах.
Почему curl_cffi и подобное — не панацея
`curl_cffi` умеет подделывать JA3 под Chrome, Safari, Firefox. Звучит как решение. На деле — есть нюанс: подделка работает на уровне ClientHello, но не на уровне поведения сессии. Chrome открывает HTTP/2-соединение и держит его. Chrome отправляет определённый набор заголовков в определённом порядке. Chrome использует определённые приоритеты потоков в HTTP/2.
Если вы подделали JA3 под Chrome, но шлёте `User-Agent: Mozilla/5.0 (Linux; Android 13)...` и при этом не открываете HTTP/2 push, не шлёте `sec-ch-ua` в правильном порядке — это несоответствие. TikTok сверяет TLS-отпечаток с HTTP-отпечатком (порядок заголовков, наличие `sec-fetch-*`, `priority`). Расхождение — второй красный флаг.
Пример рабочего запроса через curl_cffi с эмуляцией Chrome:
```python
from curl_cffi import requests
session = requests.Session(
impersonate="chrome124",
proxies={
"https": "socks5h://[2a01:4f8:1c1c:abcd::1]:1080"
}
)
resp = session.get(
"https://www.tiktok.com/api/user/detail/?uniqueId=test",
headers={
"accept-language": "en-US,en;q=0.9",
"sec-ch-ua": '"Chromium";v="124", "Google Chrome";v="124"',
}
)
print(resp.status_code, resp.http_version)
```
Здесь `socks5h` — важно: `h` означает, что DNS-резолв идёт через прокси, а не локально. Иначе утечка DNS-запросов выдаст реальную инфраструктуру.
Ротация IPv6 внутри сессии и TLS session tickets
Ещё одна ловушка — ротация адреса посреди TLS-сессии. Прокси-пул выдаёт новый IPv6 каждые N минут. Клиент переподключается. При reconnect браузер шлёт session ticket из предыдущей сессии — это нормально для настоящего пользователя. Но если адрес сменился, а тикет тот же и пришёл с другого /64 — TikTok видит несоответствие.
Реальный Chrome на мобильном при смене сети (Wi-Fi → LTE) теряет тикеты и делает полный хендшейк. Скрипт с прокси-ротацией часто этого не делает — переиспользует тикет. Это ловится.
Решение: либо не ротировать адрес внутри сессии вообще, либо при ротации сбрасывать TLS-состояние полностью. `requests.Session` кеширует соединения через urllib3 pool — нужно явно закрывать пул при смене прокси.
```python
import requests
def fresh_session(proxy_url):
s = requests.Session()
s.proxies = {"https": proxy_url}
s.headers.update({"Connection": "close"})
return s
for i, proxy in enumerate(proxy_pool):
s = fresh_session(proxy)
try:
r = s.get("https://www.tiktok.com/", timeout=10)
finally:
s.close()
```
`Connection: close` убивает keep-alive и заставляет делать новый хендшейк каждый раз. Медленнее, зато чище с точки зрения отпечатка.
Что видит TikTok на уровне HTTP/2
Если TLS-слой прошёл проверку — начинается HTTP/2. Здесь TikTok смотрит на: порядок псевдозаголовков (`:method`, `:path`, `:authority`, `:scheme`), наличие `priority`-кадров, размеры window update, наличие `SETTINGS` с определёнными параметрами. Настоящий Chrome шлёт SETTINGS с `INITIAL_WINDOW_SIZE=6291456` и `MAX_CONCURRENT_STREAMS=1000`. Go-клиент — другие значения. Python httpx — третьи.
Совпадение JA4 с Chrome + несовпадение HTTP/2 SETTINGS с Chrome = детект. Это называется HTTP/2 fingerprint (или Akamai fingerprint). Инструменты вроде `curl_cffi` его частично эмулируют, но не полностью — особенно на уровне приоритетов потоков.
Проверить свой отпечаток можно через публичные сервисы (tls.peet.ws, browserleaks). Если ваш клиент даёт JA4 Chrome, но HTTP/2 fingerprint — Python-httpx, вы уже светитесь.
Как выглядит нормальная архитектура фермы
Не «один прокси = один аккаунт». Скорее так: один аккаунт = один профиль браузера (со своим fingerprint seed) + один /64 + один TLS-стек. Профили изолированы, куки не пересекаются, TLS-сессии не шарятся между профилями.
Для мобильных аккаунтов — реальные Android-эмуляторы с OkHttp, каждый со своим набором сертификатов и своим сетевым стеком. Для десктопных — headless Chrome с разными версиями и разными платформами (Win/Mac/Linux). Для API-сценариев — curl_cffi с разными `impersonate`-профилями, но не более 5-10 аккаунтов на один профиль.
IPv6-прокси здесь — расходник. Через lexic.ml удобно раздавать /64-подсети на профили: каждый профиль получает свой пул адресов, ротация внутри пула не пересекается с другими профилями. Это снимает проблему «двадцать аккаунтов из одной подсети» без ручного менеджмента адресов.
Кейс: подмена JA3 без подмены HTTP/2
Команда взяла curl_cffi, настроила `impersonate="chrome120"`, прогнала трафик через пул IPv6. Первую неделю — тишина. На восьмой день — бан восьми аккаунтов из пятнадцати.
Причина: `curl_cffi` на тот момент эмулировал ClientHello, но HTTP/2 fingerprint оставался от libcurl. TikTok сгруппировал аккаунты по HTTP/2-отпечатку (он совпадал у всех), сопоставил с TLS-отпечатком (он был Chrome-подобный) и увидел: «пятнадцать Chrome-клиентов с идентичным HTTP/2-стеком libcurl». Живых пользователей с таким сочетанием не бывает.
Решение: смешать реальные браузеры (Playwright с разными профилями) и curl_cffi. Плюс — рандомизировать версии Chrome, чтобы HTTP/2 SETTINGS отличались. Через месяц — стабильно.
Кейс: DNS-утечка через IPv6
Отдельная история. Клиент настроил SOCKS5-прокси с IPv6-адресом, но DNS-резолв оставил локальным. Запросы к `www.tiktok.com` шли через прокси, а резолв — через локальный резолвер провайдера. TikTok видит: соединение с IPv6-адреса X, но SNI-запрос пришёл после DNS-запроса из сети Y.
Формально это не детект мультиаккаунта — это детект прокси. Но в связке с несколькими аккаунтами картина складывается. Решение — `socks5h` вместо `socks5`, либо `proxy_resolver` в клиенте:
```bash
curl -x socks5h://[2a01:4f8:1c1c:abcd::1]:1080 \
--resolve www.tiktok.com:443:[2a01:4f8:1c1c:abcd::1] \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)..." \
https://www.tiktok.com/api/user/detail/?uniqueId=test
```
`--resolve` жёстко привязывает домен к адресу, минуя DNS. `socks5h` гарантирует, что резолв идёт на стороне прокси. Двойная защита от утечки.
Кейс: session resumption через разные /64
Клиент ротировал IPv6 каждые 10 минут. Сессии TikTok жили дольше. При reconnect клиент слал session ticket, выданный с адреса A, но подключался с адреса B из другой /64. TikTok помечал это как «подозрительное возобновление» и через неделю снимал аккаунты.
Причина — несоответствие между TLS-сессией и сетевым путём. Настоящий пользователь при смене сети теряет тикеты. Скрипт — нет.
Решение: при смене прокси — полный сброс TLS-состояния, включая тикеты. В Python это означает новый `Session` и отсутствие кеша соединений. В браузерах — новый контекст (не просто новая вкладка). В curl — `--no-sessionid` и `--no-keepalive`.
Что делать, если аккаунты уже начали падать
Первое — проверить отпечатки. Прогнать трафик через tls.peet.ws и сравнить JA4 + HTTP/2 fingerprint с эталонным Chrome/OkHttp. Если хеши совпадают у разных «устройств» — вот и причина.
Второе — развести TLS-стеки. Не «подделать под Chrome», а реально использовать разные клиенты: Chrome, Firefox, Safari (через Playwright), OkHttp (через эмулятор), curl_cffi с разными профилями. Каждый профиль — не больше 5-10 аккаунтов.
Третье — развести сеть. Один /64 на профиль, не на аккаунт. Ротация внутри /64 — можно. Между /64 — только с полным сбросом TLS.
Четвёртое — синхронизировать поведение. Если профиль заявляет Android — он должен вести себя как Android: правильные заголовки, правильные интервалы, правильные размеры запросов. Расхождение между TLS-отпечатком и поведением — самый частый триггер.
Итог
Детект мультиаккаунтов в TikTok — это не про IP. IP — лишь один из слоёв. Основной сигнал — TLS-отпечаток и его согласованность с HTTP/2-отпечатком, поведением сессии и сетевым путём. IPv6-прокси решает задачу адресации, но не решает задачу отпечатка. Если двадцать аккаунтов ходят через один Python-скрипт — неважно, двадцать у них разных IPv6 или один.
Работающая схема — это изоляция на всех уровнях: сеть, TLS, HTTP/2, поведение. Прокси-пул с /64 на профиль, разные TLS-стеки, разные HTTP/2-профили, синхронизированное поведение. Дороже, медленнее, зато аккаунты живут.