Бот может подделать User-Agent, эмулировать движения мыши и подменить canvas fingerprint. Но он не может изменить то, как его TLS-библиотека представляется серверу при установке соединения. Именно на этом основана технология JA3 fingerprinting — метод идентификации клиентов по параметрам TLS-рукопожатия, который работает до того, как загрузится хотя бы один байт вашей страницы. В этой статье мы разберём, как устроены отпечатки JA3 и JA4, почему они надёжнее JavaScript-проверок и как reverse proxy может использовать их для фильтрации ботов в реальном времени.
Что происходит до загрузки страницы: TLS ClientHello
Прежде чем браузер получит HTML, CSS или JavaScript, он устанавливает зашифрованное соединение с сервером. Первое сообщение в этом процессе — ClientHello. Это пакет, который клиент отправляет серверу в самом начале TLS-рукопожатия (handshake). ClientHello содержит набор параметров, которые описывают криптографические возможности клиента.
Основные поля ClientHello
- TLS Version — версия протокола, которую поддерживает клиент (TLS 1.2 = 0x0303, TLS 1.3 = 0x0304).
- Cipher Suites — упорядоченный список шифронаборов, которые клиент готов использовать. Каждый шифронабор — это комбинация алгоритмов шифрования, хеширования и обмена ключами.
- Extensions — расширения протокола: поддержка SNI, ALPN, supported_versions, signature_algorithms, key_share и десятки других.
- Elliptic Curves (Supported Groups) — список эллиптических кривых для обмена ключами (x25519, secp256r1, secp384r1 и другие).
- EC Point Formats — форматы представления точек на эллиптических кривых (как правило, uncompressed).
Каждая TLS-библиотека формирует ClientHello по-своему. Chrome, Firefox, Safari, curl, Python requests, Go net/http — все они отправляют разные наборы параметров в разном порядке. Именно эта уникальность и делает ClientHello источником отпечатка.
Важный момент: ClientHello отправляется до установки шифрованного канала. Это открытый текст, доступный любому промежуточному узлу — и в первую очередь reverse proxy перед вашим сервером. В отличие от HTTP-заголовков, которые формируются на уровне приложения и легко подделываются, параметры ClientHello определяются криптографической библиотекой, встроенной в клиент. Разработчик бота не контролирует эти параметры, если не модифицирует саму TLS-библиотеку — а это задача совершенно другого уровня сложности.
Как работает JA3 fingerprinting: от ClientHello до хеша
JA3 — метод, разработанный командой Salesforce (John Althouse, Jeff Atkinson, Josh Atkins — отсюда название) в 2017 году. Идея элегантна в своей простоте: взять пять полей из ClientHello, объединить их в строку и вычислить MD5-хеш.
Формула JA3
Из ClientHello извлекаются пять параметров:
- TLSVersion — числовое значение версии протокола.
- Ciphers — список cipher suites, разделённых дефисом.
- Extensions — список расширений, разделённых дефисом.
- EllipticCurves — список поддерживаемых кривых.
- EllipticCurvePointFormats — форматы точек.
- Протокол (t = TCP, q = QUIC)
- Версия TLS (12 = TLS 1.2, 13 = TLS 1.3)
- Наличие SNI (d = есть, i = нет)
- Количество cipher suites (двузначное число)
- Количество extensions (двузначное число)
- Первое значение ALPN (h2 = HTTP/2, h1 = HTTP/1.1)
- Клиент инициирует TLS-подключение к вашему домену.
- Reverse proxy перехватывает ClientHello и вычисляет JA3/JA4-хеш.
- Хеш сравнивается с базой известных отпечатков: легитимные браузеры, известные боты, автоматизированные инструменты.
- На основании результата proxy принимает решение: пропустить, заблокировать, отправить на CAPTCHA или пометить для аналитики.
- Всё это происходит за миллисекунды — пользователь не замечает никакой задержки.
- curl и Python requests используют системную OpenSSL, которая предлагает широкий набор cipher suites — это мгновенный маркер нечеловеческого трафика при обращении к веб-ресурсу.
- Go net/http имеет характерный минимализм — всего 5 cipher suites. Любой бот на Go немедленно идентифицируется. Go — популярный язык для написания парсеров и скрейперов, поэтому его отпечаток встречается в бот-трафике непропорционально часто.
- Puppeteer очень близок к Chrome, но отличия всё равно есть — достаточные для надёжной классификации. Ключевое различие обычно в наборе extensions и порядке их перечисления.
- Firefox использует библиотеку NSS, которая формирует принципиально другой ClientHello — с другим набором cipher suites, другим порядком extensions и поддержкой расширений, которых нет в Chrome (например, delegated_credentials). Это делает Firefox легко отличимым от Chrome-based клиентов.
- JA3/JA4 — идентификация TLS-стека клиента.
- HTTP/2 fingerprint — порядок фреймов SETTINGS, WINDOW_UPDATE, HEADERS.
- Поведенческий анализ — паттерны навигации, скорость взаимодействия.
- JavaScript fingerprint — canvas, WebGL, AudioContext (для тех, кто прошёл TLS-проверку).
- Patched TLS-библиотеки — модификация BoringSSL или OpenSSL для имитации конкретного отпечатка. Библиотеки вроде utls (Go) или curl-impersonate позволяют это делать.
- Антидетект-браузеры — коммерческие решения, которые подменяют TLS-стек вместе с браузерным fingerprint.
- Использование реальных браузеров — запуск обычного Chrome через CDP (Chrome DevTools Protocol) с кастомным профилем.
Эти пять полей объединяются через запятую в одну строку — JA3 raw string:
TLSVersion,Ciphers,Extensions,EllipticCurves,ECPointFormats
Для реального Chrome эта строка выглядит примерно так:
771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,65281-16-10-35-43-51-13-45-28-27-17513-21,29-23-24,0
Здесь 771 — это TLS 1.2 (0x0303 = 771 в десятичной системе). Далее идут cipher suites в том порядке, в каком их перечисляет Chrome, затем расширения, кривые и формат точек.
Затем от этой строки берётся MD5-хеш — и это финальный JA3-отпечаток:
cd08e31494f9531f560d64c695473da9
Этот хеш стабилен для конкретной версии конкретного клиента. Chrome 120 на Windows и Chrome 120 на macOS дадут одинаковый JA3, потому что обе платформы используют один и тот же встроенный BoringSSL. Но Chrome и Firefox — разные, поскольку Firefox использует NSS (Network Security Services) — совершенно другую криптографическую библиотеку с собственным набором предпочтений. Chrome и Puppeteer с модифицированным порядком cipher suites — тоже разные. Даже минорные обновления браузера могут изменить JA3, если в новую версию добавлена поддержка нового cipher suite или расширения.
GREASE: ловушка для неаккуратного парсинга
В 2016 году Google ввёл механизм GREASE (Generate Random Extensions And Sustain Extensibility). Суть: в ClientHello добавляются специальные «мусорные» значения из зарезервированного диапазона — 0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a, 0x8a8a, 0x9a9a, 0xaaaa, 0xbaba и другие.
Эти значения случайны при каждом соединении. Их цель — протестировать серверы на устойчивость к неизвестным расширениям и cipher suites. Но для fingerprinting они создают проблему: если учитывать GREASE, один и тот же браузер будет давать разные отпечатки при каждом подключении.
Поэтому при вычислении JA3 все GREASE-значения отфильтровываются. Это документировано в спецификации JA3 и обязательно для корректной реализации. Любая система, которая не удаляет GREASE перед хешированием, будет генерировать нестабильные отпечатки и принимать ложные решения.
JA4: следующее поколение TLS-отпечатков
JA3 отлично справляется с базовой идентификацией, но у него есть ограничения. MD5-хеш необратим — по нему нельзя понять, какие именно параметры использовал клиент. Два совершенно разных набора параметров теоретически могут дать один и тот же хеш (коллизия). Кроме того, JA3 чувствителен к порядку cipher suites и extensions, а некоторые библиотеки рандомизируют этот порядок.
JA4, разработанный FoxIO (John Althouse — один из авторов JA3), решает эти проблемы. JA4 — это не один хеш, а структурированный отпечаток из нескольких секций.
Структура JA4
JA4-отпечаток состоит из трёх частей, разделённых символом подчёркивания:
Часть 1 — JA4_a (метаданные):
Компактное текстовое описание клиента:
Пример: t13d1516h2 — TCP, TLS 1.3, SNI есть, 15 cipher suites, 16 extensions, HTTP/2.
Часть 2 — JA4_b (cipher suites):
SHA-256 (первые 12 символов) от отсортированного списка cipher suites. Сортировка устраняет зависимость от порядка — даже если библиотека рандомизирует порядок cipher suites, хеш останется стабильным.
Часть 3 — JA4_c (extensions + signature algorithms):
SHA-256 (первые 12 символов) от двух блоков: отсортированные extensions и отдельно — значения signature_algorithms.
Итоговый JA4 выглядит так:
t13d1516h2_8daaf6152771_e5627efa2ab1
Что JA4 улучшает по сравнению с JA3
| Характеристика | JA3 | JA4 |
|---|---|---|
| Алгоритм хеширования | MD5 (уязвим к коллизиям) | SHA-256 (криптографически стойкий) |
| Зависимость от порядка | Да — порядок cipher suites влияет на хеш | Нет — списки сортируются перед хешированием |
| Читаемость | Только хеш, ничего не видно | Метаданные читаемы: протокол, версия, количества |
| Signature Algorithms | Не учитываются | Отдельная секция в JA4_c |
| Гранулярность | Один хеш на всё | Три независимые секции для гибкого сравнения |
Благодаря сортировке JA4 устойчив к рандомизации порядка, которую всё чаще применяют современные TLS-библиотеки. А читаемая первая секция позволяет быстро понять, с чем вы имеете дело, даже без полной базы отпечатков.
Почему headless-браузеры не могут скрыть TLS-отпечаток
Когда бот использует Puppeteer или Playwright, он управляет настоящим Chromium. На уровне JavaScript всё выглядит как обычный Chrome — User-Agent совпадает, navigator.webdriver скрыт, canvas fingerprint подменён. Но TLS-рукопожатие происходит на уровне TLS-стека операционной системы или встроенной библиотеки (BoringSSL в случае Chromium).
Вот в чём проблема: headless-браузеры часто используют другую сборку Chromium, скомпилированную с другими флагами. Это приводит к тому, что список cipher suites или extensions отличается от стандартного Chrome. Даже если различие минимально — например, отсутствие одного расширения или изменённый порядок кривых — JA3-хеш будет совершенно другим.
Конкретный пример
Стандартный Chrome может отправить cipher suites в таком порядке:
4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53
А Puppeteer, запущенный с bundled Chromium, может дать:
4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53-10
Одно лишнее значение — и JA3-хеш полностью меняется. Никакой User-Agent spoofing это не скроет, потому что TLS-рукопожатие завершается за сотни миллисекунд до того, как JavaScript получит возможность что-либо подменить.
Есть и другая категория различий. Headless-режим Chromium может не загружать определённые расширения TLS, связанные с визуальным рендерингом или пользовательским интерфейсом. Некоторые модификации Puppeteer отключают расширение delegated_credentials или изменяют параметры key_share. Каждое такое отличие — это изменённый бит в JA3-строке и, как следствие, совершенно другой MD5-хеш.
Антидетект-браузеры (Multilogin, GoLogin, Dolphin) пытаются решить эту проблему, подменяя TLS-стек. Некоторые из них используют patched BoringSSL, чтобы эмулировать отпечаток конкретной версии Chrome. Но это требует постоянного обновления при каждом релизе Chrome, и любое отставание мгновенно выдаёт подделку. На практике между выходом новой версии Chrome и обновлением антидетект-браузера проходит от нескольких дней до нескольких недель — окно, в котором бот легко обнаруживается.
JA3 fingerprinting на reverse proxy: детекция до загрузки страницы
Ключевое преимущество TLS-отпечатков — они доступны на самом раннем этапе обработки запроса. Reverse proxy (Nginx, HAProxy, Envoy или кастомный сервер) может извлечь JA3/JA4 из ClientHello до проксирования запроса к бэкенду.
Как это работает на практике
При использовании технологии eBPF (extended Berkeley Packet Filter) можно анализировать ClientHello на уровне ядра операционной системы — ещё до того, как пакет дойдёт до user-space приложения. Это даёт минимальную задержку и возможность обрабатывать десятки тысяч соединений в секунду.
Важно: JA3/JA4-хеш записывается в лог вместе с каждым запросом. Это позволяет ретроспективно анализировать трафик — например, обнаружить, что 40% визитов с определённого JA3-отпечатка приходят от автоматизированного инструмента, а не от настоящего Chrome.
Сравнение TLS-отпечатков: кто есть кто
Ниже приведена таблица, показывающая, как различные клиенты идентифицируются через JA3 fingerprinting. Конкретные хеши зависят от версии, ОС и настроек, но принцип стабилен: каждый тип клиента даёт свой уникальный отпечаток.
| Клиент | TLS Version | Примерное кол-во Cipher Suites | JA3-хеш (пример) | Характерные признаки |
|---|---|---|---|---|
| Chrome 120+ | TLS 1.3 | 15 | cd08e31494f9531f560d64c695473da9 |
GREASE-значения, расширение compressed_certificate |
| Firefox 121+ | TLS 1.3 | 17 | 579ccef312d18482fc42e2b822ca2430 |
Delegated credentials, расширение record_size_limit |
| Puppeteer (bundled) | TLS 1.3 | 15-16 | b32309a26951912be7dba376398abc3b |
Похож на Chrome, но отличается порядком extensions |
| curl (OpenSSL) | TLS 1.2/1.3 | 18-24 | 456523fc94726331a4d5a2e1d40b2cd7 |
Широкий набор cipher suites, нет GREASE |
| Python requests | TLS 1.2 | 11-31 | 3b5074b1b5d032e5620f69f9f700ff0e |
Зависит от версии OpenSSL в системе |
| Go net/http | TLS 1.3 | 5 | a0e9f5d64349fb13191bc781f81f42e1 |
Минимальный набор cipher suites |
Обратите внимание на несколько закономерностей:
Практические сценарии применения JA3 fingerprinting
Защита рекламного трафика от скликивания
Рекламные боты кликают по объявлениям, расходуя бюджет рекламодателя. JavaScript-проверки они научились обходить. Но если reverse proxy фиксирует, что клик пришёл от клиента с JA3-отпечатком curl или Go — это не человек. Даже если бот использует headless Chromium, различия в TLS-отпечатке позволяют выделить автоматизированный трафик.
Защита API от парсинга
Парсеры часто используют Python requests или Go-клиенты. Их JA3-отпечатки радикально отличаются от браузерных. Даже если парсер подставляет User-Agent Chrome, его TLS-стек сообщает правду.
Аудит трафика и аналитика
JA3/JA4-отпечатки можно собирать пассивно, не блокируя ни одного запроса. Это позволяет провести аудит: какой процент трафика приходит от реальных браузеров, а какой — от автоматизированных инструментов. Результаты часто удивляют: доля ботов в некоторых сегментах достигает 40-60%.
Пассивный режим особенно ценен на старте: вы подключаете сбор отпечатков, накапливаете данные за несколько дней и получаете объективную картину без какого-либо риска для легитимных пользователей. Только после анализа данных вы принимаете решение о блокировке конкретных отпечатков или категорий клиентов.
Многоуровневая защита
TLS-отпечатки наиболее эффективны в комбинации с другими сигналами:
Каждый уровень отсеивает свою категорию ботов. TLS-fingerprinting — первый и самый быстрый барьер.
Можно ли обойти JA3/JA4 fingerprinting
Было бы нечестно утверждать, что TLS-fingerprinting невозможно обмануть. Существуют подходы:
Но каждый из этих методов существенно повышает стоимость атаки. Обычный скрипт на Python requests заменяется сложной инфраструктурой с поддержкой patched библиотек, резидентных прокси и постоянного обновления отпечатков. Для массового скликивания это делает атаку экономически невыгодной.
Кроме того, JA4 с его сортировкой и структурированным форматом усложняет точную имитацию — нужно воспроизвести не только список cipher suites, но и signature algorithms, extensions с корректными значениями, а также поведение на уровне HTTP/2-фреймов.
Здесь работает экономический принцип: чем дороже имитация, тем меньше ботов будут её реализовывать. JA3/JA4 fingerprinting не претендует на то, чтобы остановить 100% ботов. Его задача — резко поднять порог входа, отсекая массовый автоматизированный трафик дешёвых ботов, которые составляют подавляющее большинство фродовых визитов. Оставшиеся сложные боты обрабатываются следующими уровнями защиты — HTTP/2 fingerprinting, поведенческим анализом и машинным обучением на совокупности сигналов.
Проверьте свой TLS-отпечаток
Увидеть собственный TLS-отпечаток можно на специализированных демо-страницах, которые анализируют ClientHello при подключении и показывают JA3-хеш, JA4-строку и все параметры TLS-рукопожатия. Такие инструменты визуализируют каждое поле ClientHello: версию протокола, полный список cipher suites с расшифровкой имён алгоритмов, все расширения, эллиптические кривые и GREASE-значения.
Простой эксперимент: откройте демо-страницу с TLS-анализом в обычном Chrome, а затем — в режиме инкогнито, в Firefox или через командную строку с curl. Вы увидите, насколько отличаются отпечатки даже при одинаковом User-Agent. Chrome и Firefox дадут принципиально разные хеши. curl покажет отпечаток, не похожий ни на один браузер. А если вы запустите Puppeteer и откроете ту же страницу — его отпечаток будет близок к Chrome, но не идентичен.
Этот эксперимент наглядно демонстрирует, почему TLS fingerprinting работает: каждый клиент оставляет уникальный след ещё до того, как сервер отправит хотя бы один байт контента.
Заключение
JA3 и JA4 fingerprinting — технология, которая работает на самом фундаментальном уровне сетевого стека. Она не зависит от JavaScript, не требует загрузки страницы и не может быть обойдена стандартными средствами автоматизации. В комбинации с eBPF-обработкой на reverse proxy эта технология обеспечивает детекцию ботов за миллисекунды — до того, как вредоносный трафик дойдёт до вашего приложения.
Для бизнеса это означает конкретные результаты: снижение расходов на фродовый рекламный трафик, защита API от парсинга, объективная картина качества трафика на основе данных, а не предположений.