Headless-браузеры: как боты притворяются людьми и почему JavaScript-проверки не работают

Типичная картина: антифрод-система докладывает, что всё чисто — JavaScript-проверки пройдены, User-Agent нормальный, куки есть, canvas fingerprint совпадает. И при этом 30% бюджета уходит на клики, которые никогда не превратятся в заявки. Как так получается? Ответ — headless-браузеры.

Что такое headless-браузер

Headless-браузер — это полноценный браузер (Chrome, Firefox), запущенный без графического интерфейса. Он загружает страницы, выполняет JavaScript, обрабатывает cookie, рендерит DOM — делает всё то же самое, что обычный браузер, просто без окна на экране.

Изначально headless-режим создавался для разработчиков: автоматизированного тестирования, скриншотов страниц, CI/CD пайплайнов. Но те же инструменты отлично работают для автоматизации кликов по рекламе.

Главные игроки:

  • Puppeteer — библиотека от Google для управления Chrome через DevTools Protocol. Стандарт индустрии.
  • Playwright — более современный инструмент от Microsoft, поддерживает Chrome, Firefox и WebKit.
  • Selenium — старейший фреймворк, поддерживает любой браузер через WebDriver.
  • Антидетект-браузеры — Multilogin, Dolphin Anty, AdsPower. Форки Chrome с патчами на уровне C++ кода.

Почему headless так сложно поймать на JS-проверках

Когда антифрод-система проверяет посетителя через JavaScript, она задаёт браузеру вопросы: User-Agent, размер экрана, WebGL, canvas fingerprint, WebRTC, движение мыши, куки. Headless Chrome честно отвечает на все эти вопросы — потому что это настоящий Chrome. Тот же движок Blink, тот же V8, та же реализация WebGL.

Проблема navigator.webdriver

Исторически headless-браузеры выдавали себя одним флагом: navigator.webdriver === true. Антифрод-системы начали его проверять. Операторы ботов ответили просто:

// Отключить флаг webdriver в Puppeteer
await page.evaluateOnNewDocument(() => {
    Object.defineProperty(navigator, 'webdriver', {
        get: () => undefined
    });
});

Один вызов — и детектор больше не видит headless.

puppeteer-extra-plugin-stealth

Сейчас существуют целые библиотеки для маскировки. Самая популярная — puppeteer-extra-plugin-stealth. Патчит более 10 свойств браузера: скрывает navigator.webdriver, подделывает navigator.plugins, исправляет Permissions API, добавляет поддельный WebGL Vendor/Renderer, имитирует window.chrome объект.

После этого плагина большинство JS-детекторов не видят headless Chrome вообще.

Что реально выдаёт headless: сетевой уровень

При всей изощрённости маскировки у headless-браузеров есть уязвимости, которые невозможно закрыть патчингом JavaScript.

TCP fingerprint не совпадает с профилем

Бот запускает headless Chrome на сервере с Ubuntu 22.04. Puppeteer-stealth скрывает все JS-признаки, User-Agent установлен в Chrome 120 on Windows 11. Но:

  • TCP Window Size при первом SYN-пакете = 65535 (Linux), а у реального Windows 11 = 64240
  • TTL исходящих пакетов = 64 (Linux), а не 128 (Windows)
  • TCP Options в SYN — Linux-порядок, отличный от Windows

Эти параметры формируются ядром Linux на сервере, и никакой JavaScript их не изменит.

JA3/JA4 TLS fingerprint

При установке HTTPS-соединения браузер отправляет TLS ClientHello — набор cipher suites, расширений и их порядок строго специфичен для каждого браузера. Chrome на Linux через Puppeteer имеет другой JA3 fingerprint, чем Chrome на Windows нативно. Сигнатура «Chrome 120 on Linux» при заявленном Windows — красный флаг.

HTTP/2 SETTINGS fingerprint

При установке HTTP/2 соединения клиент отправляет SETTINGS фрейм с уникальными параметрами. Значения и порядок различаются для каждого браузера и ОС. Никакой JS-патч не изменит, как сетевой стек формирует HTTP/2 фреймы.

Иерархия надёжности методов детекции

МетодНадёжностьОбходится?
User-AgentОчень низкаяТривиально
IP-блокировкаНизкаяЛегко (прокси)
navigator.webdriverНизкаяpuppeteer-stealth
Canvas/WebGL fingerprintСредняяАнтидетект-браузеры
Поведенческий анализСредняяИмитация движений
TCP fingerprintВысокаяТребует патч ядра ОС
TLS (JA3/JA4) fingerprintВысокаяТребует кастомного браузера
Комбинация сетевых отпечатковОчень высокаяПрактически невозможно

Итоги

Барьер входа в headless-фрод стал минимальным: Docker-контейнер, 10 VPS, ротация прокси — дело нескольких часов для любого джуниор-разработчика при стоимости инфраструктуры $50–200 в месяц.

Именно поэтому JS-детекция как основной метод защиты обречена. Атакующий всегда может изучить, что именно проверяется, и адаптировать бот. Сетевой стек — нет. Детекция должна начинаться на уровне TCP/TLS — до того, как браузер успел выполнить хоть строчку JavaScript.

Scroll to Top