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

Как прокси ломает ваш TLS-отпечаток

Как прокси ломает ваш TLS-отпечаток

Отпечатки пальцев в цифровом мире — не метафора. 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 — готовьтесь к тому, что отпечаток будет от библиотеки прокси, и это может палить.

Проверяйте свои отпечатки до запуска парсеров. Это сэкономит часы отладки и нервы.

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