Как прокси ломает ваш TLS-отпечаток
Содержание
- Что такое JA3 и JA4 на пальцах
- Как прокси вмешивается в рукопожатие
- Когда прокси палит автоматизацию
- MITM-прокси и их отпечатки
- Реальный кейс: парсинг с прокси и бан по JA3
- Как прокси меняет отпечаток при CONNECT
- Кейс: nginx как reverse proxy и смена отпечатка
- Паттерн ClientHello и его аномалии
- Кейс: мобильное приложение и прокси
- Как проверить свой отпечаток
- Стратегии маскировки
- Что в итоге
Отпечатки пальцев в цифровом мире — не метафора. JA3 и JA4 — это хэши, которые антифрод-системы снимают с вашего TLS-рукопожатия. Прокси меняет их. Вопрос — как именно и можно ли это скрыть.
Механизм простой: клиент отправляет ClientHello с набором параметров — версия TLS, список cipher suites, расширения, порядок их следования. Сервер считает хэш от этого набора. Python-скрипт с `requests` даёт один JA3. Браузер Chrome — другой. Через прокси — третий. И вот тут начинаются проблемы.
Что такое JA3 и JA4 на пальцах
JA3 — это MD5-хэш от конкатенации полей ClientHello: SSL/TLS версия, cipher suites, extensions, эллиптические кривые, форматы точек. Появился в 2017 году как инструмент для Threat Intelligence. JA4 — эволюция от FoxIO, вышел в 2023-м. Он не хэширует всё подряд, а раскладывает по категориям и использует более короткие идентификаторы.
JA4 устроен умнее. Он учитывает порядок cipher suites, а не просто их наличие. Плюс отдельно выделяет SNI, ALPN и другие параметры. Серверу проще сравнивать JA4, потому что он разбит на сегменты: `t13d1516h2_8daaf6152771_02713d6af862`. Формат `протокол_версия_набор_расширения`.
Разница критична для прокси. JA3 можно было обойти простым копированием набора cipher suites. JA4 ловит не только набор, но и порядок, что делает подделку сложнее.
Как прокси вмешивается в рукопожатие
Прокси бывают двух типов: туннелирующие и терминирующие. Первые (SOCKS5, многие HTTP-прокси в режиме CONNECT) просто пересылают зашифрованный трафик. Они не трогают ClientHello. Вторые — MITM-прокси — расшифровывают, анализируют, а потом устанавливают своё соединение с целевым сервером.
Вот пример для туннелирующего прокси:
```bash
curl --proxy socks5://127.0.0.1:1080 https://api.example.com/v1/check
```
Здесь TLS-сессия устанавливается между вашим клиентом и сервером. Прокси — слепой перевозчик. JA3 остаётся родным.
Другое дело — MITM. Например, корпоративный прокси на базе Squid с SSL-Bump. Он подменяет сертификат, устанавливает два TLS-соединения: с вами и с сервером. Ваш реальный JA3 видит только прокси. Сервер получает JA3 самого прокси.
Когда прокси палит автоматизацию
Автоматизация палится не из-за прокси как такового. Палится несоответствие. Сервер ожидает от браузера Chrome JA4 с определёнными параметрами, а получает от прокси Python-скрипта совсем другой набор.
Пример: скрипт на Python с библиотекой `httpx` использует OpenSSL. Его ClientHello имеет характерные признаки: порядок cipher suites от OpenSSL, отсутствие расширений brotli, zstd, отсутствие `GREASE`-значений. Прокси добавляет свои заголовки, но TLS-отпечаток остаётся от OpenSSL. Антифрод-система видит: JA4 от OpenSSL + заголовки от прокси + User-Agent от Chrome. Несостыковка.
```python
import httpx
proxies = {
"http://": "http://user:pass@proxy.lexic.ml:8080",
"https://": "http://user:pass@proxy.lexic.ml:8080"
}
client = httpx.Client(proxies=proxies)
resp = client.get("https://target-site.com/api/check")
print(resp.json())
```
Такой запрос даст JA4 от OpenSSL 3.x. Если сервер ожидает Chrome 120+ — бан.
MITM-прокси и их отпечатки
Когда прокси терминирует TLS, он использует свою библиотеку TLS. Чаще всего это OpenSSL или BoringSSL (если прокси написан на Go). Каждая библиотека оставляет характерный след.
| Библиотека | Типичный JA4 | Особенности |
|---|---|---|
| OpenSSL 1.1.1 | `t12d1516h2_...` | Старый порядок cipher suites, нет modern extensions |
| OpenSSL 3.x | `t13d1516h2_...` | Добавлен TLS 1.3, но порядок наследуется |
| BoringSSL (Go) | `t13d2222h2_...` | Свой порядок, GREASE-значения |
| NSS (Firefox) | `t13d1a1ah2_...` | Уникальный набор расширений |
Прокси на Go (например, goproxy) даёт JA4 от BoringSSL. Прокси на Python (mitmproxy) — от OpenSSL. Если вы притворяетесь Chrome, но ваш прокси работает на Go — сервер увидит JA4 от BoringSSL и заподозрит неладное.
Реальный кейс: парсинг с прокси и бан по JA3
Пример: задача — собирать данные с сайта объявлений. Сайт использует Cloudflare. Первая попытка — Python requests через SOCKS5-прокси. Результат: 403 на втором запросе. Логи Cloudflare показывают JA3, который не совпадает ни с одним известным браузером.
Решение — использовать реальный браузер через Playwright с прокси:
```python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
headless=False,
proxy={"server": "http://proxy.lexic.ml:8080"}
)
context = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
)
page = context.new_page()
page.goto("https://target-site.com")
print(page.title())
browser.close()
```
Chromium использует BoringSSL. Его JA4 близок к настоящему Chrome, но не идентичен: Playwright добавляет расширение `chrome` с номером версии. Cloudflare это видит. Но если сайт не слишком параноидальный — пропускает.
Как прокси меняет отпечаток при CONNECT
Режим CONNECT — это когда клиент сам устанавливает TLS-соединение через прокси. Прокси работает как туннель. Он не видит содержимое, но видит метаданные: IP назначения, время соединения, объём трафика.
Отпечаток JA3/JA4 здесь остаётся клиентским. Но появляется другой признак — TLS-сессия устанавливается с IP прокси. Если прокси засвечен в базах как датацентровый — бан. Если прокси резидентный — шансы выше.
Ключевой момент: при CONNECT прокси не ломает TLS, но ломает "чистоту" IP. Сервер видит TLS от Chrome с IP, который принадлежит хостинг-провайдеру. Для антифрода это сигнал.
Кейс: nginx как reverse proxy и смена отпечатка
Пример: вы крутите свой сервис, который ходит на внешний API. Сначала ходите напрямую — работает. Решили завернуть через nginx как reverse proxy для логирования. И получили бан.
```nginx
server {
listen 443 ssl;
server_name api.internal;
ssl_certificate /etc/nginx/certs/server.crt;
ssl_certificate_key /etc/nginx/certs/server.key;
location / {
proxy_pass https://external-api.com;
proxy_ssl_server_name on;
proxy_ssl_name external-api.com;
}
}
```
Nginx при установке соединения с внешним API использует OpenSSL. Его JA4 отличается от того, что отправлял ваш Python-скрипт через `requests` (тоже OpenSSL, но другая версия и конфигурация). Внешний API увидел новый отпечаток и посчитал это атакой.
Решение — либо не проксировать TLS (использовать туннель), либо настраивать `ssl_ciphers` и `ssl_protocols` так, чтобы имитировать целевой клиент. Полная имитация сложна, но приблизительное совпадение часто проходит.
Паттерн ClientHello и его аномалии
Антифрод-системы смотрят не только на итоговый хэш. Они анализируют сам ClientHello. Например, порядок cipher suites. Chrome ставит TLS_AES_128_GCM_SHA256 первым в TLS 1.3. Python-скрипт через OpenSSL может поставить TLS_CHACHA20_POLY1305_SHA256 первым. Это уже аномалия.
Ещё один признак — расширения. Chrome отправляет `application_settings`, `compressed_certificate`, `delegated_credentials`, `ech_config_list`. OpenSSL по умолчанию — нет. Прокси не добавляет эти расширения, потому что не знает о них. Отпечаток получается "беднее".
```bash
openssl s_client -connect example.com:443 -servername example.com -tlsextdebug
```
Вывод покажет, какие расширения поддерживает ваша OpenSSL. Сравните с тем, что отправляет Chrome (через DevTools или Wireshark). Разница будет разительной.
Кейс: мобильное приложение и прокси
Мобильное приложение для iOS использует Network.framework. Его TLS-стек отличается от desktop. Если вы гоняете трафик приложения через прокси с десктопа — отпечаток не совпадёт с ожидаемым для мобильной версии.
Пример: приложение банка проверяет JA4 на своём API. Ожидает JA4 от iOS 17 с определёнными параметрами. Вы перехватываете трафик через mitmproxy на десктопе и пересылаете через прокси. Сервер банка видит JA4 от OpenSSL 3.0 — мгновенный бан аккаунта.
Решение — использовать прокси, который поддерживает проброс мобильного трафика без терминирования TLS. Например, SOCKS5 с мобильного устройства. Тогда отпечаток остаётся родным.
Как проверить свой отпечаток
Есть сервисы, которые показывают ваш JA3/JA4. Например, `tls.peet.ws` или `ja3er.com`. Через прокси:
```bash
curl --proxy http://proxy.lexic.ml:8080 https://tls.peet.ws/api/all
```
Ответ покажет ваш JA3, JA4, а также детальную разбивку ClientHello. Сравните ответы через разные прокси и напрямую. Разница в JA4 покажет, что прокси вмешивается.
```bash
curl https://tls.peet.ws/api/all | jq '.ja4'
curl --proxy socks5://127.0.0.1:1080 https://tls.peet.ws/api/all | jq '.ja4'
```
Если значения совпадают — прокси работает в режиме туннеля. Если нет — он терминирует TLS.
Стратегии маскировки
Полностью скрыть факт проксирования TLS нельзя. Можно только приблизить отпечаток к ожидаемому. Варианты:
1. Использовать туннелирующий прокси — JA4 остаётся клиентским.
2. Использовать браузерные движки (Playwright, Puppeteer) — JA4 близок к браузерному.
3. Патчить OpenSSL — включать нужные расширения и порядок cipher suites.
4. Использовать прокси на базе BoringSSL с кастомной конфигурацией.
Ни один вариант не даёт 100% гарантии. Каждый — компромисс между скоростью, сложностью и вероятностью обнаружения.
Что в итоге
Прокси влияет на TLS-отпечаток только тогда, когда терминирует TLS. В режиме туннеля отпечаток остаётся неизменным. Проблемы с антифродом возникают из-за несоответствия между ожидаемым и фактическим отпечатком, а не из-за прокси как такового.
Для автоматизации: если нужен чистый JA4 — используйте туннелирующий прокси и браузерный движок. Если прокси терминирует TLS — готовьтесь к тому, что отпечаток будет от библиотеки прокси, и это может палить.
Проверяйте свои отпечатки до запуска парсеров. Это сэкономит часы отладки и нервы.