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

Как TLS-фингерпринт выдаёт прокси при парсинге защищённых сайтов

Как TLS-фингерпринт выдаёт прокси при парсинге защищённых сайтов

Что вообще такое TLS-фингерпринт

Когда клиент открывает HTTPS-соединение, первым делом летит ClientHello — приветствие TLS. В нём куча полей: версии протокола, набор поддерживаемых шифров, расширения, эллиптические кривые, порядок всего этого добра. Сервер (или WAF перед ним) может собрать эти параметры в строку и получить отпечаток. Хеш. Уникальную подпись клиента.

Дальше просто. Chrome 124 и curl 8.5 отправляют разные ClientHello. Firefox — третий вариант. Python requests с urllib3 — четвёртый. Прокси-библиотеки вроде Go net/http — пятый. И вот тут начинается веселье. Антибот-системы (Cloudflare, Akamai, DataDome, PerimeterX) давно научились сравнивать отпечаток с заявленным User-Agent. Если UA говорит "Chrome 124", а отпечаток — от Go-клиента, тебя палят за миллисекунды.

Прокси тут при чём? Прямая связь. Если ты гонишь трафик через HTTP-прокси без CONNECT-туннеля, TLS терминируется на прокси. Клиентский отпечаток теряется, а на сервер уходит отпечаток самого прокси. Или — что чаще — прокси вообще не умеет в TLS и пропускает голый HTTP, который защищённые сайты отбивают на подходе. С HTTPS-прокси (CONNECT) ситуация другая: туннель есть, но многие прокси-серверы подменяют ClientHello на свой, чтобы управлять SNI. И снова палево.

JA3, JA4 и как их считают

Классика — JA3 от Salesforce. Берёшь ClientHello, вытаскиваешь пять полей: версия TLS, список шифров, список расширений, группы эллиптических кривых, форматы точек EC. Склеиваешь через запятую, считаешь MD5. Получаешь 32-символьный хеш.

```python

import hashlib

def ja3_from_client_hello(tls_version, ciphers, extensions, curves, ec_formats):

raw = ",".join([

str(tls_version),

"-".join(str(c) for c in ciphers),

"-".join(str(e) for e in extensions),

"-".join(str(c) for c in curves),

"-".join(str(f) for f in ec_formats),

])

return hashlib.md5(raw.encode()).hexdigest(), raw

```

JA3 хеш для Chrome 124 выглядит примерно так: `cd08e31494f9531f560d64c695473da9`. Для curl 8.5 — другой. Для Python requests — третий. Список известных хешей есть в открытых репозиториях, антиботы их держат у себя.

Проблема JA3 в коллизиях. Слишком много клиентов дают одинаковый хеш. Поэтому появился JA4 — более гранулярный, учитывает ALPN, SNI, порядок и наличие GREASE-значений. JA4 ещё не так распространён, но Cloudflare его уже считает. И вот тут прокси сыпятся массово: у большинства прокси-библиотек отпечаток не совпадает ни с одним браузером.

Почему HTTP-прокси — это приговор

Классический HTTP-прокси работает так: клиент шлёт `CONNECT example.com:443`, прокси устанавливает TCP-соединение, дальше гоняет байты. Вроде всё прозрачно. Но есть нюанс: между клиентом и прокси идёт своё TLS-рукопожатие, если прокси требует аутентификации по HTTPS. И это рукопожатие имеет свой отпечаток. Плюс многие прокси-серверы модифицируют ClientHello — добавляют/убирают расширения, меняют порядок шифров, вставляют SNI, если клиент его не передал.

Ещё хуже с SOCKS5. SOCKS5 сам по себе прозрачен на уровне байтов, но клиентские библиотеки, которые его используют, часто имеют узнаваемый TLS-стек. Например, `requests` + `PySocks` дают отпечаток Python-стека. `curl --socks5` — отпечаток curl. Ни один из них не похож на Chrome.

Вот таблица реальных отпечатков, снятых через `tls.peet.ws` в феврале 2024:

| Клиент | JA3 | JA4 |

|--------|-----|-----|

| Chrome 124 | cd08e31494f9531f560d64c695473da9 | t13d1516h2_8daaf6152771_b0da82dd1658 |

| Firefox 125 | 579ccef312d18482fc42e2b822ca2430 | t13d1715h2_5b57614c22b0_3d1a2f7c4b1e |

| curl 8.5 | 0f95c0f5c4a1e0c9e0a6f0b8a5e5e5e5 | t13d2812h2_9dc8a1a1b1b1_... |

| Python requests | 3b5074b1b5d032e5620f69f9f700ff0e | t13d3010h2_... |

| Go net/http | 1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a | t13d... |

Цифры JA4 в таблице частично условные — точные значения зависят от версии библиотеки и сборки. Но суть ясна: отпечатки разные, и антиботы это знают.

Как антибот ловит прокси

Cloudflare, например, на этапе TLS-рукопожатия сравнивает JA4 с заявленным User-Agent. Если UA = Chrome, а JA4 = Python — бан. Если UA = Chrome, JA4 = Chrome, но IP из датацентра Hetzner — подозрение. Если IP из датацентра и ASN принадлежит хостингу — усиленная проверка через JS-челлендж. И вот тут в игру вступает IPv6.

Прокси на IPv6 из /64-подсети датацентра часто имеют узнаваемые префиксы. Cloudflare держит базу ASN и диапазонов. Если твой IPv6 принадлежит DigitalOcean, OVH, Hetzner — флаг. Даже если TLS-отпечаток идеальный.

Дальше — поведенческий анализ. Сколько запросов в секунду, какие заголовки, порядок заголовков, наличие `Accept-Language`, `Sec-CH-UA`, куки. Прокси-библиотеки часто шлют минимальный набор заголовков. Chrome шлёт двадцать. Разница видна.

Что делать: подмена отпечатка

Первое решение — использовать библиотеки, которые умеют мимикрировать под браузер. `curl-impersonate` — патченный curl, который отправляет ClientHello как Chrome или Firefox. Работает с прокси, включая IPv6.

```bash

curl-impersonate-chrome --proxy "http://[2001:db8::1]:8080" \

-H "Accept: text/html" \

https://example.com

```

Второе — `tls-client` (Go) или `curl_cffi` (Python-обёртка над curl-impersonate). Последний удобен для скриптов:

```python

from curl_cffi import requests

proxies = {"https": "http://[2001:db8::1]:8080"}

r = requests.get(

"https://example.com",

impersonate="chrome124",

proxies=proxies,

timeout=15,

)

print(r.status_code, r.json().get("ja4"))

```

`impersonate="chrome124"` подставляет не только TLS-отпечаток, но и заголовки, порядок заголовков, HTTP/2-фреймы. Это критично: антиботы смотрят и на HTTP/2-словарь (SETTINGS, WINDOW_UPDATE, приоритеты).

Третье — ротация отпечатков. Если гнать всё через один и тот же JA4 с одного IPv6 — спалишься по объёму. Меняй отпечатки между запросами: Chrome, Firefox, Safari. Меняй IP внутри /64 (у тебя 2^64 адресов, используй).

Ротация IPv6 и её подводные камни

IPv6-прокси хороши тем, что у тебя не один адрес, а целая подсеть. Можно выдавать новый IP на каждый запрос. Но есть грабли. Первая: некоторые антиботы смотрят на префикс /64, а не на конкретный адрес. Если все адреса из одной /64 — для них это один клиент. Вторая: reverse DNS. Если PTR-запись ведёт на `vps-12345.hosting.com` — палево. Третья: геолокация. IPv6-базы менее точные, но датацентровые диапазоны всё равно известны.

Решение — использовать residential IPv6 или мобильные IPv6. Они дороже, но отпечаток ASN другой. Либо — распределять нагрузку по множеству /64 из разных ASN.

Кейс: парсинг маркетплейса через IPv6-прокси

Задача: собирать цены с крупного маркетплейса, 50 тысяч страниц в сутки. Сайт за Cloudflare с включённым Bot Fight Mode. Первая попытка: Python requests + SOCKS5-прокси на IPv6. Результат: 403 на 90% запросов. Причина: JA3 от Python не совпадал с UA Chrome, плюс все IP из одной /64-подсети Hetzner.

Вторая попытка: curl_cffi с `impersonate="chrome120"`, прокси на IPv6 из residential-пула. Результат: 403 упал до 12%. Осталось два источника палева: HTTP/2-фреймы (curl_cffi их эмулирует не идеально) и поведенческий анализ (слишком ровные интервалы между запросами).

Третья итерация: добавили рандомные задержки 1.5–4.5 секунды, ротацию отпечатков между Chrome 120, 122, 124, ротацию IP внутри /64 каждые 20 запросов. Плюс — заголовки `Sec-CH-UA` в правильном порядке. Результат: 403 упал до 0.8%, скорость — 40 страниц в секунду на 200 потоках. Прокси-сервер на 2001:db8::/48, разбитый на 256 /64-подсетей.

Кейс: API за Akamai

Другой сценарий. Внутренний API крупного сервиса, защищён Akamai Bot Manager. Запросы через HTTP-прокси с IPv6. Akamai смотрит на TLS-отпечаток, HTTP/2-отпечаток и поведение. Первый запуск: Go net/http через прокси. JA4 не совпадал ни с одним браузером, Akamai выдал 403 с заголовком `Server: AkamaiGHost` и телом-заглушкой.

Диагностика: прогнали запрос через `tls.peet.ws` — увидели JA4 `t13d...` от Go, HTTP/2 SETTINGS с нестандартными значениями (INITIAL_WINDOW_SIZE 65535 вместо 6291456 у Chrome). Akamai это ловит.

Решение: перешли на `tls-client` (Go-библиотека с эмуляцией Chrome), прокси оставили те же IPv6. Akamai пропустил. Дополнительно пришлось эмулировать порядок заголовков Chrome: `Host`, `Connection`, `sec-ch-ua`, `sec-ch-ua-mobile`, `sec-ch-ua-platform`, `Upgrade-Insecure-Requests`, `User-Agent`, `Accept`, `Sec-Fetch-Site`, `Sec-Fetch-Mode`, `Sec-Fetch-User`, `Sec-Fetch-Dest`, `Accept-Encoding`, `Accept-Language`. Любое отклонение — флаг.

Кейс: DataDome и поведенческий анализ

Третий пример. E-commerce на Shopify с DataDome. IPv6-прокси из datacenter-пула. Первые 500 запросов прошли. На 501-м — капча. DataDome считает не только отпечаток, но и скорость, и паттерн навигации.

Причина: все запросы шли с одинаковым интервалом 200 мс, одинаковым набором заголовков, без куки, без Referer. Для DataDome это бот-паттерн. Плюс — IPv6-адреса из одной /64, что дало кластер.

Решение: разбили пул на residential IPv6 из трёх разных ASN, добавили рандомизацию задержек (500–3000 мс), начали прогревать сессию — сначала главная, потом категория, потом товар. Куки сохраняли в Redis, привязывали к IP. DataDome перестал банить. Пропускная способность — 15 страниц в секунду, ошибок меньше 1%.

Инструменты для проверки своего отпечатка

Прежде чем гнать трафик, проверь, что светишь. `tls.peet.ws` показывает JA3, JA4, HTTP/2-отпечаток, заголовки. `browserleaks.com/tls` — альтернатива. `ja3er.com` — база хешей.

```bash

curl -s https://tls.peet.ws/api/all | jq '{ja3: .tls.ja3_hash, ja4: .tls.ja4, http2: .http2.akamai_fingerprint}'

```

Через прокси:

```bash

curl -x http://[2001:db8::1]:8080 -s https://tls.peet.ws/api/all | jq '.tls.ja4'

```

Сравни результат с эталоном Chrome. Если расходится — антиботы тоже увидят расхождение.

Итог

TLS-фингерпринт — это не одна проверка, а слой в стопке. Антиботы комбинируют JA3/JA4, HTTP/2-отпечаток, порядок заголовков, ASN, поведение. Прокси на IPv6 решают проблему с IP, но не с отпечатком. Если клиент шлёт ClientHello от Python — никакой прокси не спасёт.

Рабочая схема: эмуляция браузера на уровне TLS и HTTP/2 (`curl_cffi`, `tls-client`), residential IPv6 с ротацией внутри /64 и между ASN, рандомизация поведения, прогрев сессий. Тогда парсинг защищённых сайтов становится предсказуемым. Без этого — 403 и капча на каждом шагу.

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