Перейти к содержимому

Почему сервер распознаёт curl по Tls-отпечатку, даже если user-agent изменён

Вы подменили User-Agent, но сервер всё равно понял, что это curl

Ситуация знакомая: интеграция обращается к партнёрскому API, но получает отказ. Разработчик меняет заголовок `User-Agent`, маскируя скрипт под Chrome, однако это не помогает. После этого обычно начинают проверять IP-адрес, частоту запросов, cookies, капчу и параметры API. Но причина может находиться гораздо раньше - ещё до появления первого HTTP-заголовка.

Сервер способен определить тип клиента на этапе TLS-рукопожатия. В этот момент приложение ещё не отправило ни `User-Agent`, ни путь запроса, ни тело сообщения. Однако клиент уже передал серверу техническое описание своего TLS-стека.

Что сервер видит до HTTP-запроса

Перед установлением защищённого соединения клиент отправляет сообщение `ClientHello`. В нём содержатся:

- поддерживаемые версии TLS;
- перечень шифров;
- расширения протокола;
- эллиптические кривые;
- форматы точек;
- алгоритмы цифровой подписи;
- список протоколов ALPN, например `h2` или `http/1.1`.

Эти параметры формирует не бизнес-логика приложения и не значение заголовка `User-Agent`. Их определяет TLS-библиотека, на которой работает клиент. В Chrome обычно используется BoringSSL, в Firefox - NSS, в системных компонентах Windows - Schannel, а в командных утилитах встречаются OpenSSL и GnuTLS.

Поэтому программа может написать в HTTP-запросе что угодно, но сам TLS-стек при этом останется прежним. Сервер увидит не только заявленное имя клиента, но и характерный набор параметров, с которым это имя может не совпадать.

Как работает TLS-отпечаток

Для компактного описания параметров TLS применяются специальные схемы отпечатков, в частности JA3 и JA4. Они превращают содержимое `ClientHello` в короткую последовательность признаков или хеш.

Условное обозначение JA4 можно представить так:

- версия TLS;
- тип соединения;
- количество шифров;
- количество расширений;
- используемый протокол ALPN.

Например, браузер может выглядеть как `t13d1516h2`, а командная утилита - как `t13d9013h2`. В первом случае клиент предлагает около пятнадцати шифров и шестнадцать расширений, во втором - девяносто шифров и тринадцать расширений.

Разница возникает не случайно. Универсальная утилита часто стремится сохранить совместимость с максимально широким кругом серверов и поэтому предлагает большой набор вариантов. Современный браузер, напротив, использует более тщательно отобранный и регулярно обновляемый список.

Сам `User-Agent` при этом может быть одинаковым:

```http
User-Agent: Mozilla/5.0 Chrome/...
```

Но TLS-отпечаток останется характерным для конкретной библиотеки и способа формирования соединения.

Почему JA4 появился после JA3

Ранний подход JA3 учитывал порядок элементов в `ClientHello`. Это было удобно, но создало проблему: современные браузеры могут перемешивать расширения, чтобы затруднить стабильную идентификацию. В результате один и тот же Chrome при разных подключениях иногда получал разные JA3-хеши.

В JA4 часть значений перед хешированием сортируется. Благодаря этому отпечаток становится устойчивее между соединениями. При этом некоторые параметры, например порядок алгоритмов подписи, сохраняются: он часто зависит от конкретной TLS-библиотеки и тоже помогает различать клиенты.

Специальные GREASE-значения, предназначенные для проверки совместимости реализаций, в подсчёт не включаются. Числовые поля ограничены двумя знаками: если клиент поддерживает больше 99 шифров или расширений, значение всё равно отображается как `99`.

Главный признак - не сам curl, а противоречие

Обнаружение конкретного отпечатка ещё не означает, что сервер точно знает название программы. Один и тот же TLS-профиль может встречаться у разных приложений, библиотек и прокси.

Гораздо полезнее проверять согласованность признаков. Например:

- `User-Agent` заявляет Chrome под Windows;
- TLS-параметры похожи на OpenSSL;
- набор расширений характерен для Python-библиотеки;
- ALPN и дальнейшее поведение соединения не соответствуют браузеру.

Такое противоречие выглядит подозрительнее, чем любой отдельный отпечаток. Настоящий браузер обычно формирует согласованный набор признаков. Если же заголовки говорят одно, а TLS - другое, сервер получает основание считать запрос автоматизированным.

При этом несоответствие может указывать не только на скрипт. Его причиной бывает корпоративный прокси, антивирус или шлюз, который расшифровывает и заново устанавливает TLS-соединение. Для защитной системы это тоже важная информация: конечный клиент и устройство, открывшее соединение, могут быть разными.

Что именно даёт такая проверка защите

TLS-отпечаток доступен до обработки HTTP. Сервер может принять решение ещё на сетевой границе:

- разрешить соединение;
- отправить проверку;
- ограничить частоту;
- направить запрос в отдельный контур анализа;
- разорвать соединение до запуска приложения.

Это снижает нагрузку на API и помогает фильтровать массовую автоматизацию раньше, чем будут потрачены ресурсы на авторизацию, маршрутизацию и обработку данных.

Однако отпечаток не является персональным идентификатором. Все пользователи одной версии браузера могут иметь одинаковый профиль. Он подходит для классификации клиентов, но не для точного опознания конкретного человека.

Кроме того, отпечаток меняется после обновления браузера, TLS-библиотеки или операционной системы. Поэтому постоянный список "разрешённых" значений быстро устаревает. Надёжнее использовать его как один из сигналов риск-модели, а не как безусловный пропуск или запрет.

Почему отпечаток нельзя просто удалить

Cookie можно очистить, хранилище - сбросить, а заголовок - заменить. С TLS-отпечатком всё иначе. Он не хранится у пользователя в виде отдельного файла и не отправляется как самостоятельный параметр.

Это результат анализа сообщения `ClientHello`, без которого защищённое соединение не состоится. Чтобы "изменить" отпечаток, нужно не удалить данные, а использовать другой TLS-клиент, прокси или специальную библиотеку, способную воспроизводить профиль нужного приложения.

Но простая подмена одного набора параметров редко делает клиент похожим на настоящий браузер. Сервер может дополнительно оценивать:

- порядок TLS-расширений;
- поддержку HTTP/2;
- формат заголовков;
- порядок псевдозаголовков;
- поведение при редиректах;
- наличие cookies;
- повторное использование соединений;
- задержки между запросами;
- обработку ошибок;
- JavaScript-сигналы и особенности выполнения сценариев.

Поэтому маскировка только под Chrome через `User-Agent` обычно даёт слабый эффект.

Что проверить разработчику, если API блокирует запросы

Начать стоит с диагностики, а не с бесконечной смены заголовков. Полезно сравнить рабочий запрос из браузера и запрос интеграции по нескольким уровням:

1. TLS-версия и набор шифров.
2. ALPN и фактически выбранный протокол.
3. Поддержка HTTP/2.
4. Тип и порядок заголовков.
5. Частота и распределение запросов.
6. Повторное использование TCP- и TLS-соединений.
7. IP-адрес, ASN и география.
8. Наличие корректной авторизации и cookies.
9. Коды ответа и момент, на котором появляется блокировка.

Если сервер отвечает отказом ещё до HTTP или соединение закрывается во время TLS, изменение `User-Agent` не решит проблему. Возможно, партнёр фильтрует TLS-профили, применяет сетевую политику или блокирует конкретный диапазон адресов.

Для легитимной интеграции правильнее использовать официальный клиент, согласовать список разрешённых адресов, настроить лимиты и предоставить партнёру понятное описание трафика. Иногда достаточно зарегистрировать сервисный идентификатор или включить отдельный режим доступа для машинных запросов.

Как строить защиту без ложных срабатываний

Запрещать весь curl или все соединения с OpenSSL - плохая стратегия. Эти инструменты применяются в мониторинге, CI/CD, резервном копировании и внутренних сервисах. Жёсткое правило может заблокировать нормальных пользователей и инфраструктуру.

Лучше объединять несколько независимых сигналов:

- TLS-отпечаток;
- согласованность с `User-Agent`;
- репутацию адреса;
- поведение по времени;
- количество ошибок;
- особенности авторизации;
- повторяемость маршрутов;
- результаты дополнительных проверок.

Система должна учитывать контекст. Один машинный запрос с корректным токеном и ожидаемого адреса не равен тысячам однотипных обращений с изменяющимися заголовками.

Итог

Подмена `User-Agent` меняет только часть HTTP-запроса. До неё сервер уже может увидеть TLS-параметры, характерные для конкретной библиотеки или типа клиента. Именно поэтому curl, Python-скрипт или другой автоматизированный инструмент иногда распознаются даже при внешнем сходстве с браузером.

TLS-отпечаток не является уникальным паспортом пользователя и не даёт абсолютной гарантии идентификации. Но в сочетании с анализом HTTP, сетевой репутации и поведения он позволяет заранее отделять браузеры от библиотек, выявлять несогласованные запросы и принимать решение ещё до запуска прикладной логики.

Прокрутить вверх