Если вы анализировали TLS-отпечатки реального трафика, то наверняка замечали странные записи в cipher suites и extensions: значения вроде 0x7a7a, 0xbaba, 0xeaea, которые не соответствуют ни одному известному шифронабору или расширению. Это не ошибка парсера и не мусорные данные. Это GREASE — механизм, который Chrome намеренно внедряет в каждое TLS-соединение. Он был разработан, чтобы защитить развитие протокола TLS, но побочным эффектом стал один из самых надёжных сигналов для детекции ботов. В этой статье мы разберём, как устроен GREASE на уровне RFC, почему он делает нестабильным классический JA3-хеш, и как антифрод-системы используют его как мета-сигнал для отделения настоящих браузеров от имитаций.
Проблема, которую решает GREASE: окостенение протокола
Протокол TLS проектировался как расширяемый. Поля ClientHello — cipher suites, extensions, supported groups, supported versions — задуманы как списки, в которые можно добавлять новые значения по мере развития криптографии. На бумаге всё просто: клиент отправляет список поддерживаемых параметров, сервер выбирает из них подходящий, неизвестные значения игнорирует. На практике десятилетия развёртывания показали, что реальность гораздо жёстче.
Protocol Ossification: почему TLS перестал развиваться
Как серверы и middlebox ломают расширяемость
На пути между браузером и сервером стоят десятки устройств: балансировщики нагрузки, WAF, DPI-системы, корпоративные прокси. Многие из них были написаны с жёсткими ожиданиями относительно содержимого ClientHello. Когда в 2016-2018 годах шла активная работа над TLS 1.3, разработчики Chrome и Firefox столкнулись с массовой проблемой: промежуточные устройства, увидев незнакомые расширения или версии протокола, просто обрывали соединение. Не возвращали ошибку — полностью дропали TCP-сессию.
Это явление получило название protocol ossification — окостенение протокола. Формально TLS расширяем, но фактически любое новое значение в ClientHello рискует сломать соединения для миллионов пользователей. Развитие протокола оказалось заблокировано не спецификацией, а реальной инфраструктурой.
Идея GREASE: постоянный шум как прививка
В 2016 году Дэвид Бенджамин из команды Chrome/BoringSSL предложил элегантное решение: если серверы и middlebox не умеют игнорировать неизвестные значения, нужно заставить их научиться. Решение — постоянно отправлять заведомо неизвестные значения в каждом соединении. Если сервер или промежуточное устройство не может их проигнорировать, оно сломается сразу, а не через два года, когда IETF утвердит новое расширение.
Этот подход получил имя GREASE — Generate Random Extensions And Sustain Extensibility. В 2019 году он был формализован как RFC 8701.
Как работает GREASE: зарезервированные значения и правила внедрения
RFC 8701 определяет набор зарезервированных значений, которые клиент может вставлять в различные поля ClientHello. Эти значения выбраны так, чтобы их было легко отличить от реальных параметров, но при этом они выглядели как обычные двухбайтовые идентификаторы.
Зарезервированные значения GREASE
Набор значений для cipher suites, extensions, supported versions и supported groups:
0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a, 0x4a4a,
0x5a5a, 0x6a6a, 0x7a7a, 0x8a8a, 0x9a9a,
0xaaaa, 0xbaba, 0xcaca, 0xdada, 0xeaea, 0xfafa
Паттерн очевиден: старший и младший байты совпадают, а младшие четыре бита всегда 0x0a. Всего 16 значений. Для ALPN-протоколов зарезервированы двухбайтовые строки с тем же паттерном. Для PskKeyExchangeModes — однобайтовые значения (0x0B, 0x2A, 0x49, 0x68, 0x87, 0xA6, 0xC5, 0xE4).
Где именно Chrome вставляет GREASE
При формировании ClientHello браузер на основе BoringSSL случайным образом выбирает значение из зарезервированного набора и вставляет его в несколько полей:
- Cipher Suites — одно GREASE-значение добавляется в начало списка шифронаборов.
- Extensions — GREASE-значение добавляется как идентификатор расширения (с пустым телом).
- Supported Versions — GREASE-значение добавляется перед реальными версиями протокола.
- Supported Groups (Elliptic Curves) — GREASE-значение добавляется в список поддерживаемых групп.
- Signature Algorithms — GREASE-значение может появиться в списке алгоритмов подписи.
- Google Chrome (все платформы)
- Chromium и все производные: Edge, Opera, Brave, Vivaldi, Yandex Browser
- Android WebView (использует BoringSSL)
- Electron-приложения (основаны на Chromium)
curl— использует OpenSSL/LibreSSL/NSS, GREASE не включён по умолчанию- Python
requests/urllib3— используют OpenSSL через binding - Go
net/http— собственная реализация crypto/tls без GREASE - Node.js (до определённых версий) — OpenSSL без GREASE
- Java
HttpClient— JSSE без GREASE - Большинство кастомных TLS-библиотек и скриптов для автоматизации
- ClientHelloOuter — внешняя часть, видимая промежуточным устройствам. Содержит минимальный набор параметров и зашифрованное тело.
- ClientHelloInner — внутренняя часть, зашифрованная публичным ключом сервера. Содержит реальные параметры соединения, включая SNI.
Критический момент: значения выбираются случайно при каждом новом соединении. При одном подключении к серверу Chrome может отправить 0x7a7a в cipher suites и 0xbaba в extensions, а при следующем — 0x3a3a и 0xdada. Позиция GREASE-значения в списке также может меняться.
Реальный пример из данных fingerprint-сервера
Рассмотрим фрагмент реального ClientHello от Chrome 131, полученного нашим eBPF-сборщиком:
{
"cipher_suites": [
{"id": 31354, "hex": "0x7a7a", "grease": true},
{"id": 4865, "hex": "0x1301", "name": "TLS_AES_128_GCM_SHA256"},
{"id": 4866, "hex": "0x1302", "name": "TLS_AES_256_GCM_SHA384"},
{"id": 4867, "hex": "0x1303", "name": "TLS_CHACHA20_POLY1305_SHA256"},
...
],
"extensions": [
{"id": 47802, "hex": "0xbaba", "grease": true},
{"id": 0, "name": "server_name"},
{"id": 23, "name": "extended_master_secret"},
...
],
"supported_versions": [
{"hex": "0xeaea", "grease": true},
{"hex": "0x0304", "name": "TLS 1.3"},
{"hex": "0x0303", "name": "TLS 1.2"}
],
"supported_groups": [
{"id": 35466, "hex": "0x8a8a", "grease": true},
{"id": 29, "name": "x25519"},
{"id": 23, "name": "secp256r1"},
{"id": 24, "name": "secp384r1"}
]
}
GREASE-значения стоят первыми в каждом списке. Сервер обязан их проигнорировать и выбрать первый реальный параметр. Но при следующем соединении того же браузера вместо 0x7a7a в cipher suites может оказаться 0x2a2a или 0xdada — набор из 16 значений выбирается случайно каждый раз.
GREASE и TLS fingerprinting: проблема нестабильных хешей
Появление GREASE создало серьёзную проблему для TLS fingerprinting. Классический метод JA3 берёт значения cipher suites и extensions, конкатенирует их в строку и вычисляет MD5-хеш. Когда GREASE-значения меняются при каждом подключении, меняется и входная строка — а значит, каждый запрос от одного и того же Chrome даёт уникальный JA3-хеш.
Как JA3 и JA4 справляются с GREASE
Решение прямолинейное: перед построением отпечатка все GREASE-значения удаляются из данных. Алгоритм проверяет каждое значение в cipher suites, extensions, supported versions и supported groups: если оно соответствует паттерну (value & 0x0f0f) == 0x0a0a, это GREASE, и его нужно исключить.
JA3 фильтрует GREASE-значения из всех пяти компонентов отпечатка перед конкатенацией и хешированием. После фильтрации Chrome даёт стабильный JA3-хеш вне зависимости от конкретных GREASE-значений в конкретном соединении.
JA4 пошёл дальше. Помимо фильтрации GREASE, JA4 также сортирует extensions и cipher suites, что устраняет зависимость от порядка элементов (который Chrome тоже может варьировать). Результат — ещё более стабильный отпечаток, устойчивый к перестановкам.
Но сам факт необходимости фильтрации GREASE привёл к неожиданному инсайту: наличие или отсутствие GREASE в ClientHello — это сигнал само по себе.
GREASE как инструмент детекции ботов
Парадокс GREASE в контексте антифрода: механизм, созданный для защиты расширяемости протокола, оказался одним из самых надёжных маркеров настоящего браузера. Причина проста — GREASE реализован в BoringSSL, криптографической библиотеке, которую использует Chromium. Если клиент не основан на BoringSSL, он не отправляет GREASE.
Кто отправляет GREASE, а кто — нет
Отправляют GREASE:
Не отправляют GREASE:
Это создаёт чёткую границу. Бот, который представляется как Chrome 131 через User-Agent, но использует Python requests для HTTP-запросов, не отправит ни одного GREASE-значения в ClientHello. Его TLS-отпечаток мгновенно выдаёт несоответствие: заголовки говорят «Chrome», а рукопожатие — «Python/OpenSSL».
Почему подделка GREASE — нетривиальная задача
Теоретически разработчик бота может модифицировать TLS-библиотеку, чтобы добавить GREASE-значения. Но это требует вмешательства на уровне формирования ClientHello — глубже, чем настройка HTTP-заголовков. Кроме того, недостаточно просто добавить случайное GREASE-значение. Нужно воспроизвести точное поведение BoringSSL: правильные позиции в списках, корректный набор полей, куда вставляются значения, правильное распределение выбора из 16 зарезервированных значений.
Каждая ошибка в реализации создаёт аномалию, видимую для fingerprint-системы. GREASE-значение в cipher suites, но не в extensions? Аномалия. GREASE стоит не в начале списка, а в середине? Аномалия. Значение 0x0b0b, которого нет в RFC 8701? Грубая ошибка, мгновенная детекция.
GREASE как мета-сигнал: не только наличие, но и контекст
Опытная антифрод-система анализирует GREASE не бинарно (есть/нет), а как набор мета-сигналов с несколькими измерениями.
Позиция GREASE в списках
Chrome помещает GREASE-значение первым элементом в cipher suites и supported groups. Если fingerprint показывает GREASE в середине или в конце списка, это сигнал нестандартной реализации. Возможно, перед нами модифицированный BoringSSL или попытка ручной имитации.
Какие конкретные значения используются
RFC 8701 допускает любое из 16 значений. Chrome выбирает одно значение и использует его (или разные значения) в нескольких полях одновременно. Корреляция между GREASE-значениями в разных полях ClientHello — ещё один сигнал. Если cipher suites содержит 0x7a7a, а extensions — 0xbaba, это нормально для Chrome. Но если все четыре поля содержат одно и то же значение 0x0a0a — это подозрительный паттерн, нехарактерный для реального BoringSSL.
Консистентность с другими параметрами ClientHello
GREASE — часть общей картины. Настоящий Chrome 131 отправляет определённый набор cipher suites, определённые extensions в определённом порядке, конкретные supported groups и signature algorithms. GREASE-значения должны вписываться в эту картину. Если отпечаток показывает GREASE, но набор extensions не соответствует ни одной известной версии Chrome, перед нами или экзотический Chromium-форк, или попытка подделки.
Firefox и GREASE: различия в реализации
Firefox использует собственную криптографическую библиотеку NSS (Network Security Services), а не BoringSSL. Поддержка GREASE в NSS появилась позже и реализована иначе.
Firefox добавляет GREASE-значения в extensions и supported versions, но поведение отличается от Chrome в деталях: набор полей, куда вставляются значения, позиция в списках, частота выбора конкретных значений. Эти различия сами по себе являются дифференцирующим сигналом: даже с GREASE fingerprint Firefox отличается от fingerprint Chrome.
Для антифрод-системы это означает дополнительное измерение анализа. Бот, который отправляет GREASE в стиле Chrome, но представляется как Firefox через User-Agent, выдаёт себя несоответствием реализации GREASE. Верно и обратное: GREASE в стиле NSS при заголовках Chrome — такой же красный флаг.
Safari использует собственный стек на базе Secure Transport (и частично BoringSSL в новых версиях), что создаёт третий паттерн GREASE-поведения. Каждый браузерный движок оставляет уникальный след, и GREASE добавляет ещё одно измерение к этому следу.
Будущее: ECH и его влияние на fingerprinting
TLS продолжает развиваться, и следующий рубеж — Encrypted Client Hello (ECH). Это расширение, которое шифрует основную часть ClientHello, включая SNI (Server Name Indication) — имя домена, к которому подключается клиент. ECH разрабатывается IETF и уже поддерживается в экспериментальном режиме в Chrome и Firefox.
Что ECH меняет для fingerprinting
При использовании ECH ClientHello разделяется на две части:
Для fingerprinting на уровне промежуточного узла (middlebox, reverse proxy, CDN перед сервером) это означает, что доступна только внешняя часть. Но есть нюансы, которые сохраняют ценность TLS fingerprinting.
Во-первых, reverse proxy, принадлежащий владельцу сайта (наш сценарий), расшифровывает TLS и видит полный ClientHello. ECH скрывает данные от третьих сторон на пути, но не от конечного сервера.
Во-вторых, даже ClientHelloOuter содержит fingerprint-сигналы: набор cipher suites, список extensions, supported groups. Эти параметры по-прежнему специфичны для TLS-библиотеки. GREASE-значения присутствуют и во внешней части.
В-третьих, само наличие ECH — это сигнал. Не все клиенты поддерживают ECH, и паттерн его использования (или неиспользования) добавляет ещё одно измерение к отпечатку.
GREASE и ECH: взаимодействие механизмов
GREASE сыграл важную роль в развёртывании ECH. Именно благодаря тому, что GREASE годами «тренировал» серверы и middlebox игнорировать неизвестные расширения, добавление нового расширения ECH стало возможным без массовых сбоев. Без GREASE middlebox, не знающий расширения encrypted_client_hello, мог бы обрывать соединения — а с GREASE он уже привык пропускать неизвестные идентификаторы.
Практические выводы для антифрод-системы
GREASE — это не просто техническая деталь TLS. Для системы детекции ботов это многослойный сигнал, который работает на нескольких уровнях одновременно.
Уровень 1: бинарный фильтр. Клиент представляется Chrome, но не отправляет GREASE? Высокая вероятность бота или автоматизированного скрипта. Этот простой тест отсекает большинство примитивных ботов на Python, Go и Node.js.
Уровень 2: корректность реализации. GREASE присутствует, но позиции, значения или набор полей не соответствуют реальному BoringSSL? Перед нами продвинутый бот с частичной имитацией GREASE. Таких меньше, но они существуют.
Уровень 3: кросс-валидация. GREASE-паттерн соответствует Chrome, но набор extensions — Firefox? Или наоборот? Несоответствие между GREASE-реализацией и остальным отпечатком выдаёт гибридную подделку.
Уровень 4: статистический анализ. Распределение GREASE-значений по множеству соединений от одного клиента. Настоящий Chrome выбирает значения равномерно из 16 вариантов. Бот, который всегда отправляет одно и то же GREASE-значение, статистически аномален.
Все эти уровни работают на данных, которые доступны до загрузки страницы — на этапе TLS-рукопожатия. Никакой JavaScript, никакие cookie, никакие поведенческие модели. Чистая сетевая криптография.
Заключение
GREASE — пример того, как инженерное решение для защиты протокола от окостенения становится инструментом безопасности совершенно иного рода. Chrome добавляет случайные зарезервированные значения в ClientHello не для идентификации — а чтобы серверы и middlebox не ломались при появлении новых расширений TLS. Но побочный эффект оказался мощнее задуманного: GREASE стал одним из наиболее надёжных маркеров настоящего браузера.
Для антифрод-системы GREASE — это ещё одно измерение в пространстве TLS fingerprinting. Не замена JA3/JA4, а дополнение: мета-сигнал, который усиливает точность отпечатка и сужает пространство для подделки. Будущее TLS с ECH усложнит часть методов fingerprinting, но не обесценит их — особенно для владельцев серверов, которые расшифровывают трафик на своём reverse proxy.