Как Discord детектирует прокси по TLS-рукопожатию и что это значит для мультиаккаунтинга
Содержание
- Что вообще видит Discord
- Почему residential-прокси не спасают
- requests использует urllib3 + OpenSSL, JA3 будет стабильным
- Посмотреть свой JA3 можно через внешний сервис
- HTTP/2 fingerprint — второй слой
- Что реально работает
- IPv6 меняет расклад
- Поведенческий слой
- Кейс: ферма на 50 аккаунтов через SOCKS5
- Кейс: IPv6 /64 и GeoIP-несоответствие
- Кейс: синхронный флуд и кластерный бан
- Что проверять перед запуском
- Итог
Что вообще видит Discord
Discord — не просто чат. Это инфраструктура на Cloudflare и собственном edge, где каждый запрос проходит через кучу слоёв: TLS fingerprint, HTTP/2 fingerprint, поведенческие метрики, IP-репутация. Мультиаккаунтинг ломается не на одном уровне, а на стыке нескольких.
Начнём с базы. Когда клиент открывает TCP-соединение к `gateway.discord.gg:443`, первое, что видит сервер — ClientHello. Это ещё до всякой авторизации, до токена, до HTTP. В ClientHello лежит JA3-отпечаток. Discord его читает.
JA3 — это MD5 от конкатенации: версия TLS, список cipher suites, список extensions, список elliptic curves, формат point compression. Всё в hex, через запятую. Получается строка вроде `771,4865-4866-4867-49195-49199,0-23-65281-10-11-35-16-5-13,29-23-24,0`. MD5 от неё — и есть отпечаток.
Официальный desktop-клиент Discord (Electron + Chromium) имеет конкретный JA3. Мобильный — другой. Веб-версия в Chrome — третий. Бот на discord.py — четвёртый, потому что там aiohttp. И если ты сидишь через `curl` или `python-requests`, JA3 будет радикально отличаться от всего, что Discord ожидает увидеть.
Почему residential-прокси не спасают
Классическая схема мультиаккаунтинга: купил residential-прокси, повесил на каждый аккаунт свой IP, радуешься. Работало годами. Сейчас — нет.
Причина в том, что residential-прокси даёт тебе чистый IP, но не даёт тебе чистый TLS. Прокси-сервер терминирует TCP, а дальше пересылает байты как есть. ClientHello от твоего `python-requests` доходит до Discord без изменений. И Discord видит: IP из жилого сектора Comcast в Чикаго, а JA3 — от Python 3.11 с OpenSSL 3.0. Это несоответствие.
Хуже того. Squid, 3proxy, обычный SOCKS5 — все они работают на уровне TCP. Они не трогают TLS. Значит, любой fingerprint, который клиент отправил, долетает до целевого сервера в первозданном виде. Никакой маскировки.
Вот как выглядит типичный JA3 от `requests`:
```python
import requests
import hashlib
requests использует urllib3 + OpenSSL, JA3 будет стабильным
r = requests.get("https://discord.com/api/v10/gateway")
print(r.status_code)
Посмотреть свой JA3 можно через внешний сервис
print(requests.get("https://tls.peet.ws/api/all").json()["tls"]["ja3_hash"])
```
На выходе получишь что-то вроде `3b5074b1b5d032e5620f69f9f700ff0e`. Это отпечаток Python. Discord знает этот хеш. Он в чёрном списке.
HTTP/2 fingerprint — второй слой
Даже если ты подделал JA3, есть ещё HTTP/2 fingerprint. Он строится из:
- SETTINGS frame (какие параметры и в каком порядке)
- WINDOW_UPDATE
- PRIORITY frames
- порядка pseudo-headers (`:method`, `:authority`, `:path`, `:scheme`)
- HEADERS frame padding
Chrome отправляет `:method :authority :scheme :path`. Firefox — `:method :path :authority :scheme`. Go net/http — вообще другой порядок. И это видно на уровне фреймов, ещё до расшифровки.
Discord (как и Cloudflare) собирает эти данные в Akamai-style fingerprint. Комбинация JA3 + HTTP/2 fingerprint + IP ASN даёт очень точную картину того, кто стучится.
Подделать HTTP/2 fingerprint сложнее, чем JA3. Нужен либо кастомный TLS-стек, либо библиотека вроде `curl-impersonate`, которая эмулирует Chrome на уровне байтов.
Что реально работает
`curl-impersonate` — патченный curl, который воспроизводит TLS и HTTP/2 handshake Chrome или Firefox. Не просто заголовки, а именно байтовый поток.
```bash
curl_chrome116 -X GET "https://discord.com/api/v10/gateway" \
-H "Authorization: Bot YOUR_TOKEN" \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
--proxy socks5://user:pass@residential-proxy:1080
```
Флаг `curl_chrome116` — это wrapper-скрипт, который подставляет нужные cipher suites, extensions, порядок HTTP/2 фреймов. JA3 будет совпадать с Chrome 116. Если прокси residential и ASN чистый — Discord видит обычного пользователя Chrome.
Альтернатива — `tls-client` (Python-обёртка над Go-библиотекой). Она умеет эмулировать Chrome, Firefox, Safari, iOS.
```python
from tls_client import Session
session = Session(client_identifier="chrome_120")
session.proxies = {"https": "http://user:pass@residential:8080"}
headers = {
"Authorization": "Bot YOUR_TOKEN",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
r = session.get("https://discord.com/api/v10/users/@me", headers=headers)
print(r.status_code, r.json())
```
`client_identifier="chrome_120"` даёт JA3, идентичный реальному Chrome 120. Плюс HTTP/2 fingerprint совпадает.
IPv6 меняет расклад
Residential IPv4-прокси дорогие и медленные. IPv6-подход другой: у тебя есть /64 или /48 префикс, и ты можешь назначить каждому аккаунту уникальный адрес. Не NAT, не shared IP — реальный отдельный адрес.
Проблема в том, что многие сайты (и Discord в том числе) относятся к IPv6 настороженно. Причины:
- Многие residential-провайдеры ещё не раздают IPv6 массово. Значит, IPv6-трафик часто идёт из дата-центров.
- GeoIP для IPv6 часто кривой. Адрес может числиться в Нидерландах, а реально быть в Румынии.
- Репутация IPv6-подсетей слабая — спамеры первыми освоили большие префиксы.
Discord проверяет ASN и rDNS. Если твой IPv6 принадлежит хостингу (Hetzner, OVH, DigitalOcean) — флаг сразу. Нужны IPv6 от residential-провайдеров или мобильных операторов.
Прокси-сервис на базе IPv6 (вроде того, что делает lexic.ml) даёт именно это: большие пулы IPv6 из residential ASN, где каждый запрос может уйти с уникального адреса. Комбинация «чистый JA3 + residential IPv6 + правильный HTTP/2» проходит большинство проверок.
Поведенческий слой
Технические метрики — половина дела. Вторая половина — то, как аккаунты себя ведут.
Discord смотрит на:
- Время между регистрацией и первым сообщением. Если 3 секунды — бот.
- Паттерн активности. 24/7 онлайн без сна — подозрительно.
- Синхронность. Десять аккаунтов отправляют сообщение в одну гильдию в пределах 200 мс — ферма.
- Одинаковые аватарки, bio, никнеймы с инкрементом (`user1`, `user2`).
- IP-переходы. Аккаунт логинился с IPv4 в Германии, потом с IPv6 в Сингапуре через 5 минут — угон или прокси.
Технически идеальный JA3 не спасёт, если десять аккаунтов ведут себя как клоны. Discord банит по кластеру: нашёл один — нашёл все.
Кейс: ферма на 50 аккаунтов через SOCKS5
Пример: сервер на Ubuntu 22.04, 50 Discord-аккаунтов, каждый через свой SOCKS5-прокси от недорогого провайдера. Клиент — `discord.py` self-bot (нарушение ToS, но для понимания механики полезно).
Через 6 часов после старта — 43 аккаунта забанены. Осталось 7. Логи показали: все 43 имели одинаковый JA3 (`3b5074b1b5d032e5620f69f9f700ff0e` — Python) и одинаковый HTTP/2 fingerprint от aiohttp. IP были разные, ASN разные, но fingerprint один. Discord сгруппировал по отпечатку и вычистил пачкой.
Причина: SOCKS5 не трогает TLS. aiohttp отправляет свой ClientHello. Все 50 аккаунтов выглядят как один клиент с 50 IP.
Решение: заменить транспорт на `curl-impersonate` или `tls-client` с эмуляцией Chrome, оставить residential-прокси, добавить рандомизацию таймингов. После переделки — 4 бана из 50 за неделю, и те по поведению, не по fingerprint.
Кейс: IPv6 /64 и GeoIP-несоответствие
Другой пример: /64 префикс от европейского хостинга, 200 адресов, по одному на аккаунт. JA3 подделан через `tls-client`, всё чисто. Но баны пошли на второй день.
Причина оказалась в GeoIP. Discord резолвит IPv6 через MaxMind. Для этого /64 префикса база выдавала локацию «Frankfurt, DE». А аккаунты были зарегистрированы с европейских residential IPv4 (Польша, Чехия). Резкая смена страны при каждом логине — триггер.
Плюс rDNS: у хостинговых IPv6 он вида `static.123.45.67.89.clients.your-server.de`. Discord это видит и помечает как дата-центр.
Решение: IPv6 от residential ASN с корректным GeoIP и без хостингового rDNS. Либо вообще отказаться от IPv6 для Discord и остаться на residential IPv4, но с правильным TLS.
Кейс: синхронный флуд и кластерный бан
Третий пример. 30 аккаунтов, все с правильным JA3 Chrome, residential IPv4, разные ASN. Технически — идеально. Забанили все 30 за 12 минут.
Причина: скрипт отправлял сообщение в общий канал с интервалом 50 мс между аккаунтами. Discord видит: 30 разных «пользователей» с разных IP отправляют однотипные сообщения в одну гильдию в пределах 1.5 секунды. Это невозможно для живых людей. Кластерный бан.
Решение: рандомизация задержек (от 30 секунд до 15 минут), разные тексты сообщений, разные каналы, разные гильдии. Плюс прогрев аккаунтов — регистрация, пауза сутки, первые действия через день. Тогда 30 аккаунтов живут месяцами.
Что проверять перед запуском
Перед тем как поднимать ферму, прогони чек-лист:
- JA3 совпадает с реальным браузером. Проверь на `tls.peet.ws/api/all`.
- HTTP/2 fingerprint тоже совпадает. Там же смотри поле `http2`.
- IP не из дата-центра. Проверь ASN через `ipinfo.io` или `bgp.he.net`.
- GeoIP IP совпадает со страной аккаунта.
- rDNS не палит хостинг.
- Тайминги между действиями рандомизированы, минимум 30 секунд.
- Аккаунты прогреты, не свежесозданные.
- Поведение не синхронно — разные каналы, разные тексты.
Без любого из этих пунктов — бан. Discord не смотрит на один сигнал, он складывает всё в кучу.
Итог
Детект прокси у Discord — это не одна проверка, а конвейер. JA3, HTTP/2 fingerprint, IP-репутация, GeoIP, поведение. Ломается вся цепочка на самом слабом звене. Residential IP без маскировки TLS — бесполезен. Идеальный TLS с дата-центровым IP — тоже. Правильный TLS + residential IP + человеческое поведение — работает.
Главное правило: каждый уровень должен соответствовать легенде. Если аккаунт «обычный пользователь из Варшавы на Chrome», то и TLS, и IP, и тайминги должны это подтверждать. Любое несоответствие — зацепка.