Бот может скопировать любой HTTP-заголовок. User-Agent, Accept-Language, Cookie — всё это строки, которые формируются на уровне приложения и подменяются одной строкой кода. Но когда клиент устанавливает HTTP/2-соединение, он отправляет набор параметров, продиктованных реализацией HTTP/2-стека в конкретном браузере или библиотеке. Эти параметры — размер таблицы заголовков, начальный размер окна, поддержка server push, порядок pseudo-заголовков — различаются у каждого клиента и формируют уникальный отпечаток. В этой статье мы разберём, что именно делает HTTP/2 fingerprinting третьим уровнем сетевой идентификации, какие конкретно параметры анализируются и почему большинство ботов не способны эту проверку пройти.
HTTP/2: протокол, который изменил веб
HTTP/1.1 служил основой веба более пятнадцати лет. Но его текстовый формат, блокировка head-of-line и необходимость открывать множество TCP-соединений для параллельной загрузки ресурсов стали узким местом для современных сайтов. В 2015 году IETF утвердил стандарт RFC 7540 — HTTP/2, фундаментально изменивший архитектуру клиент-серверного взаимодействия.
Ключевые отличия HTTP/2
Бинарный формат. Вместо текстовых строк HTTP/1.1 протокол использует бинарные фреймы. Каждое сообщение разбивается на фреймы определённого типа: HEADERS, DATA, SETTINGS, WINDOW_UPDATE, PRIORITY и другие. Бинарный формат эффективнее для парсинга и менее подвержен ошибкам интерпретации.
Мультиплексирование потоков. В рамках одного TCP-соединения клиент и сервер могут одновременно передавать множество потоков (streams). Каждый HTTP-запрос — это отдельный поток с уникальным идентификатором. Это устраняет head-of-line blocking на уровне HTTP и делает ненужным открытие шести параллельных TCP-соединений, как это делал HTTP/1.1.
Сжатие заголовков (HPACK). HTTP-заголовки в каждом запросе зачастую повторяются: Host, User-Agent, Accept — одни и те же строки летают туда-сюда. HPACK (RFC 7541) решает эту проблему с помощью статической и динамической таблиц. Статическая таблица содержит 61 наиболее распространённую пару заголовок-значение. Динамическая таблица пополняется в процессе сессии. Размер динамической таблицы — один из ключевых параметров отпечатка.
Server Push. Сервер может отправить клиенту ресурсы до того, как клиент их запросит — например, CSS-файл сразу после отправки HTML. На практике эта функция оказалась сложной в управлении, и Chrome отключил её поддержку начиная с версии 106. Но параметр ENABLE_PUSH в SETTINGS frame остался частью протокола — и частью отпечатка.
Сегодня HTTP/2 обслуживает более 60% всего веб-трафика. Это доминирующий протокол для взаимодействия между браузером и сервером, и его параметры инициализации стали мощным источником идентификации клиентов.
Концепция HTTP/2 fingerprinting
При установке HTTP/2-соединения клиент первым делом отправляет connection preface — магическую строку PRI * HTTP/2.0rnrnSMrnrn, за которой следует фрейм SETTINGS. Этот фрейм содержит параметры, которыми клиент описывает свои возможности и предпочтения. Сервер отвечает собственным SETTINGS и подтверждением.
Суть HTTP/2 fingerprinting: реализации протокола в разных браузерах и HTTP-библиотеках отправляют разные значения в SETTINGS, по-разному настраивают управление потоком (flow control), используют разные схемы приоритизации и даже располагают заголовки в разном порядке. Все эти различия можно собрать в единый отпечаток, однозначно идентифицирующий HTTP-клиент.
В отличие от JavaScript fingerprinting, HTTP/2 fingerprint формируется ещё до того, как сервер отправит первый байт ответа. В отличие от HTTP-заголовков, параметры SETTINGS задаются не кодом приложения, а реализацией HTTP/2-стека — библиотеками nghttp2, net/http2, Chromium networking stack и подобными. Разработчик бота, использующий Python requests или Go net/http, не контролирует эти параметры без глубокой модификации транспортного уровня.
Пять векторов HTTP/2-отпечатка
1. SETTINGS frame: начальная конфигурация клиента
Фрейм SETTINGS — основа отпечатка. Он содержит набор пар «параметр — значение», и каждый браузер отправляет свой уникальный набор.
Параметры, определённые стандартом:
- HEADER_TABLE_SIZE (0x1) — размер динамической таблицы HPACK в байтах. Определяет, сколько ранее переданных заголовков клиент может хранить для повторного использования.
- ENABLE_PUSH (0x2) — разрешает или запрещает server push. Значение 0 означает отказ от push, 1 — разрешение.
- MAX_CONCURRENT_STREAMS (0x3) — максимальное количество одновременных потоков, которое клиент готов обрабатывать.
- INITIAL_WINDOW_SIZE (0x4) — начальный размер окна управления потоком для каждого отдельного стрима (в байтах). Определяет, сколько данных сервер может отправить до получения WINDOW_UPDATE.
- MAX_FRAME_SIZE (0x5) — максимальный размер payload одного фрейма.
- MAX_HEADER_LIST_SIZE (0x6) — максимальный допустимый размер несжатого блока заголовков.
Каждый браузер выбирает свою комбинацию значений. Более того, браузеры отправляют разное количество параметров и в разном порядке — не все клиенты включают все шесть параметров в свой SETTINGS.
2. WINDOW_UPDATE frame: начальный размер окна соединения
После отправки SETTINGS клиент обычно сразу отправляет WINDOW_UPDATE для потока 0 (соединение в целом). Этот фрейм увеличивает окно управления потоком на уровне всего соединения сверх значения по умолчанию (65535 байт).
Значение WINDOW_UPDATE — ещё один характерный маркер. Chrome отправляет WINDOW_UPDATE с приращением 15663105, увеличивая окно соединения до 15728640 байт (15 МБ). Firefox использует другое значение. Curl и библиотечные HTTP/2-клиенты часто не отправляют WINDOW_UPDATE вообще, оставляя размер окна по умолчанию.
3. PRIORITY и PRIORITY_UPDATE: схема приоритизации потоков
HTTP/2 изначально включал механизм приоритизации через фрейм PRIORITY и поле priority в HEADERS. Клиент мог указать зависимости между потоками (какой поток зависит от какого) и весовые коэффициенты (от 1 до 256). Эта система формировала дерево зависимостей, по которому сервер распределял пропускную способность.
Каждый браузер реализовал приоритизацию по-своему. Chrome использовал модель с 256 уровнями приоритетов, создавая сложные деревья зависимостей. Firefox применял группировку потоков с разными весами: лидеры, фолловеры, фоновые и спекулятивные потоки. Safari использовал плоскую модель с равными весами.
С появлением RFC 9218 (Extensible Prioritization) браузеры начали переходить на новую схему с параметрами urgency и incremental, передаваемыми через заголовок Priority и фрейм PRIORITY_UPDATE. Но характер приоритизации по-прежнему различается — и это по-прежнему уникальная подпись клиента.
4. Порядок pseudo-заголовков
HTTP/2 заменил строку запроса (request line) из HTTP/1.1 на pseudo-заголовки — специальные заголовки с префиксом :. Для каждого запроса передаются четыре обязательных pseudo-заголовка:
:method— метод запроса (GET, POST и т.д.):authority— хост (аналог Host в HTTP/1.1):scheme— протокол (https):path— путь запроса
Стандарт RFC 7540 требует, чтобы все pseudo-заголовки шли перед обычными заголовками, но не предписывает порядок между ними. Каждый браузер выбирает свой порядок:
- Chrome:
:method,:authority,:scheme,:path - Firefox:
:method,:path,:authority,:scheme - Safari:
:method,:scheme,:path,:authority
Три браузера — три разных порядка. Это простая, но эффективная точка идентификации. Если клиент утверждает, что он Chrome через User-Agent, но отправляет pseudo-заголовки в порядке Firefox — это мгновенный сигнал аномалии.
5. Порядок HTTP-заголовков
Помимо pseudo-заголовков, обычные HTTP-заголовки тоже располагаются в определённом порядке, характерном для каждой реализации. Chrome, Firefox и Safari отправляют Accept, User-Agent, Accept-Encoding, Accept-Language и другие заголовки в разной последовательности.
Порядок заголовков задаётся сетевым стеком браузера, а не веб-приложением. JavaScript-код на странице может добавить кастомные заголовки, но порядок стандартных заголовков определяется внутренней логикой браузера. Это делает порядок заголовков ещё одним вектором, который бот не контролирует без модификации HTTP-стека.
Конкретные отпечатки: Chrome, Firefox, Safari, curl
Рассмотрим реальные значения SETTINGS frame для основных клиентов.
| Параметр | Chrome 120+ | Firefox 121+ | Safari 17+ | curl 8.x |
|---|---|---|---|---|
| HEADER_TABLE_SIZE (0x1) | 65536 | 65536 | 4096 | — |
| ENABLE_PUSH (0x2) | 0 | — | — | — |
| MAX_CONCURRENT_STREAMS (0x3) | 1000 | — | 100 | — |
| INITIAL_WINDOW_SIZE (0x4) | 6291456 | 131072 | 2097152 | 16777216 |
| MAX_FRAME_SIZE (0x5) | — | 16384 | 16384 | — |
| MAX_HEADER_LIST_SIZE (0x6) | 262144 | — | — | — |
| WINDOW_UPDATE (conn) | 15663105 | 12517377 | 10485760 | — |
(Прочерк означает, что клиент не включает этот параметр в SETTINGS.)
Обратите внимание на различия. Chrome устанавливает INITIAL_WINDOW_SIZE в 6 МБ и явно отключает server push. Firefox использует скромное окно в 128 КБ. Safari выбирает промежуточный вариант — 2 МБ. Curl с библиотекой nghttp2 отправляет минимальный SETTINGS с INITIAL_WINDOW_SIZE 16 МБ и зачастую не включает другие параметры.
Эти различия не случайны — они отражают инженерные решения команд разработки каждого браузера. Chrome оптимизирован для высокоскоростных соединений с большими объёмами данных. Firefox более консервативен в управлении памятью. Safari балансирует между производительностью и энергоэффективностью на устройствах Apple.
Формат Akamai HTTP/2 fingerprint
Индустрия выработала стандартный формат для записи HTTP/2-отпечатков, который часто называют «Akamai fingerprint» по имени компании, внёсшей основной вклад в его разработку. Формат состоит из четырёх секций, разделённых символом |:
S[;]|WU|P[;]|PS[;]
- S — SETTINGS frame: пары параметр:значение, разделённые точкой с запятой. Например,
1:65536;2:0;4:6291456;6:262144. - WU — WINDOW_UPDATE: значение приращения окна соединения. Например,
15663105. - P — PRIORITY: дерево зависимостей потоков. Записывается как stream_id:dependency:weight через точку с запятой.
- PS — Pseudo-header order: порядок pseudo-заголовков в виде сокращений. Например,
m,a,s,pдля Chrome (:method, :authority, :scheme, :path).
Пример полного отпечатка Chrome:
1:65536;2:0;3:1000;4:6291456;6:262144|15663105|0|m,a,s,p
Этот формат позволяет компактно записать отпечаток, сравнивать его с базой известных клиентов и быстро выявлять аномалии. Если входящее соединение заявляет себя как Chrome через User-Agent, но его Akamai fingerprint не совпадает ни с одним известным отпечатком Chrome — это бот или модифицированный клиент.
Третий вектор: трёхуровневая сетевая идентификация
HTTP/2 fingerprinting не существует в изоляции. Он является третьим уровнем в системе сетевой идентификации, где каждый уровень отпечатывает разный компонент клиентского стека.
Уровень 1: TCP fingerprinting — отпечаток операционной системы. Параметры SYN-пакета (Window Size, TTL, MSS, TCP Options) задаются ядром ОС. По ним можно определить, работает ли клиент на Windows, Linux или macOS. Бот на Linux-сервере, заявляющий себя как Windows, будет разоблачён на этом уровне.
Уровень 2: TLS fingerprinting (JA3/JA4) — отпечаток TLS-библиотеки. Параметры ClientHello (cipher suites, extensions, elliptic curves) определяются TLS-библиотекой: BoringSSL в Chrome, NSS в Firefox, Secure Transport в Safari. Каждая библиотека формирует уникальный ClientHello, и его JA3/JA4-хеш однозначно идентифицирует TLS-стек.
Уровень 3: HTTP/2 fingerprinting — отпечаток HTTP-клиента. SETTINGS frame, WINDOW_UPDATE, приоритизация и порядок заголовков определяются реализацией HTTP/2 в конкретном браузере или библиотеке. Это финальный уровень идентификации, работающий после TLS-рукопожатия, но до обработки HTTP-запроса.
Вместе эти три уровня создают многослойный отпечаток, который охватывает весь путь соединения — от сетевого стека ОС через криптографическую библиотеку до HTTP-клиента. Подделка одного уровня бессмысленна, если два других выдают реальную природу клиента.
Почему боты проваливаются
Здесь проходит критическая граница между разными классами автоматизации.
Puppeteer и Playwright запускают настоящий экземпляр Chromium. Они используют тот же сетевой стек, ту же TLS-библиотеку (BoringSSL) и тот же HTTP/2-клиент, что и обычный Chrome. Их TCP-отпечаток, JA3-хеш и HTTP/2 fingerprint будут идентичны легитимному браузеру. Для детекции таких ботов нужны другие методы — поведенческий анализ, проверка среды исполнения, анализ CDP-протокола.
Python (requests, httpx, aiohttp) используют библиотеку urllib3 поверх ssl-модуля Python. HTTP/2-поддержка обеспечивается через hyper или httpx с h2. Их SETTINGS frame кардинально отличается от любого браузера: другие значения INITIAL_WINDOW_SIZE, отсутствие ENABLE_PUSH, другой порядок параметров. TLS fingerprint (JA3) тоже не совпадает — Python ssl использует OpenSSL, а не BoringSSL. Результат: тройное несовпадение на всех уровнях при совпадении User-Agent.
Go (net/http) содержит собственную реализацию HTTP/2 в пакете golang.org/x/net/http2. Она отправляет свой уникальный набор SETTINGS, использует crypto/tls (не BoringSSL) и формирует характерный TLS ClientHello. Даже при идеально скопированном User-Agent бот на Go будет иметь отпечаток, не совпадающий ни с одним браузером.
curl использует nghttp2 для HTTP/2 и может быть собран с разными TLS-бэкендами (OpenSSL, BoringSSL, GnuTLS). Его HTTP/2 fingerprint минимален — меньше параметров в SETTINGS, другой порядок pseudo-заголовков, часто отсутствует WINDOW_UPDATE. Для антифрод-системы curl — один из наиболее легко распознаваемых клиентов.
Ирония в том, что чем более «лёгким» и эффективным является инструмент автоматизации, тем легче его обнаружить. Python-скрипт на requests потребляет минимум ресурсов, но его сетевой отпечаток — TCP, TLS и HTTP/2 — не совпадает ни с одним реальным браузером. Headless-браузер потребляет гигабайты памяти, но его сетевой стек неотличим от настоящего Chrome.
Практическое значение для защиты от фрода
HTTP/2 fingerprinting дополняет TCP и TLS fingerprinting, создавая систему, в которой каждый уровень сетевого стека проверяется независимо. Для рекламодателя это означает возможность выявлять ботов ещё до того, как они получат содержимое страницы — на этапе установки соединения, без выполнения JavaScript и без влияния на пользовательский опыт.
Reverse proxy, работающий перед веб-сервером, может за микросекунды извлечь HTTP/2 fingerprint из входящего соединения, сопоставить его с базой известных клиентов и принять решение: пропустить запрос, пометить как подозрительный или заблокировать. Всё это происходит прозрачно для легитимного пользователя.
Важно понимать: ни один из трёх уровней сетевого fingerprinting не является серебряной пулей. TCP fingerprint можно обойти модификацией ядра, TLS fingerprint — пересборкой TLS-библиотеки, HTTP/2 fingerprint — использованием headless-браузера. Но комбинация всех трёх уровней с поведенческим анализом создаёт систему, в которой стоимость обхода для атакующего становится экономически нецелесообразной.
Заключение
HTTP/2 fingerprinting завершает триаду сетевой идентификации: ОС определяется по TCP, TLS-библиотека — по ClientHello, HTTP-клиент — по SETTINGS frame и порядку заголовков. Каждый из этих уровней работает до загрузки страницы, не зависит от JavaScript и не может быть подделан простой подменой строк в коде бота.
Для бизнеса, который теряет рекламный бюджет на ботов, это означает принципиально новый подход к защите — проверка на уровне протокола, а не на уровне браузерного окружения. Протокольные отпечатки не обходятся расширениями для антидетект-браузеров, не зависят от выполнения JavaScript и работают одинаково надёжно на любом устройстве и в любой сети.