Назад к блогу
Сравнение

Защита на уровне движка vs на уровне API: почему архитектура имеет значение

Сравнение трех архитектур защиты цифровых отпечатков браузера: расширения, stealth-плагины с JS-инъекцией и модификация на уровне движка. Узнайте, почему только контроль на уровне движка обеспечивает полную согласованность.

Введение

Защита цифровых отпечатков браузера реализуется в трех принципиально различных архитектурах. Каждая работает на своем уровне браузерного стека, и именно этот уровень определяет, что она может и не может контролировать.

Три подхода:

  1. Расширения браузера, которые внедряют скрипты после загрузки страницы для переопределения JavaScript-свойств
  2. JS-инъекция и stealth-плагины, которые модифицируют среду браузера до выполнения кода страницы, обычно через фреймворки автоматизации, такие как Puppeteer или Playwright
  3. Модификация на уровне движка, которая изменяет сигналы отпечатков внутри скомпилированного кода браузера, до создания любого JavaScript-контекста

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

Влияние на конфиденциальность

Неполная или несогласованная защита отпечатков может быть хуже, чем отсутствие защиты. Когда одни сигналы модифицированы, а другие нет, полученный отпечаток содержит противоречия. Браузер, который утверждает, что работает на Windows, но производит Canvas-вывод, соответствующий рендерингу Linux, становится более уникальным, а не менее. Браузер, свойство navigator.webdriver которого было переопределено через Object.defineProperty, можно идентифицировать по тому, что дескриптор свойства не соответствует нативной реализации.

Неполная защита создает уникальный "отпечаток инструмента конфиденциальности", который часто более идентифицирующий, чем исходный отпечаток, который он пытался изменить. Исследования нескольких академических групп задокументировали этот эффект: пользователи определенных расширений конфиденциальности более идентифицируемы, чем пользователи, которые вообще не принимают никаких мер.

Архитектура вашей защиты определяет, достигнете ли вы подлинной согласованности или случайно создадите новые идентифицирующие сигналы. Понимание этих архитектурных различий необходимо для принятия обоснованного решения.

Техническая основа

Подход 1: Расширения браузера

Расширения браузера работают через API WebExtensions, предоставляющий контент-скрипты, которые выполняются в JavaScript-контексте страницы. Расширение для защиты отпечатков обычно работает следующим образом:

  1. Внедряет контент-скрипт, который выполняется при document_start
  2. Использует Object.defineProperty для переопределения свойств, таких как navigator.hardwareConcurrency, navigator.platform или screen.width
  3. Оборачивает API Canvas, WebGL и Audio для модификации возвращаемых значений
  4. Перехватывает HTMLCanvasElement.prototype.toDataURL и аналогичные методы

Слабости подхода расширений:

Несогласованность дескрипторов свойств. Когда расширение переопределяет navigator.hardwareConcurrency с помощью Object.defineProperty, дескриптор свойства меняется. Геттер становится JavaScript-функцией вместо нативного геттера браузера. Эта несогласованность видна любому коду, работающему на странице, и является известной слабостью подхода расширений.

Модификация цепочки прототипов. Расширения, оборачивающие методы прототипов, оставляют следы в цепочке прототипов. Переопределенные методы идентифицируются как ненативные JavaScript-функции, а не встроенный код браузера.

Изоляция свежих контекстов. Это самая фундаментальная слабость. Расширения могут не внедрить свои переопределения в каждый контекст выполнения. Iframe, Web Worker и другие изолированные контексты могут получить доступ к оригинальным, немодифицированным значениям. Если переопределенное значение в главном фрейме отличается от оригинального значения в свежем контексте, несогласованность очевидна.

Нет контроля рендеринга. Расширения не могут изменить фактический пиксельный вывод операций Canvas, WebGL или AudioContext. Они могут перехватить API-вызовы, читающие вывод (такие как toDataURL или getImageData), но не могут изменить то, что движок рендеринга реально производит. Это означает, что любой подход, вычисляющий отпечаток по необработанному выводу рендеринга, а не через JavaScript API, увидит реальные значения устройства.

Нет контроля сетевого уровня. Расширения не могут модифицировать HTTP-заголовки, отправляемые при начальном запросе навигации. Заголовки Client Hints, такие как Sec-CH-UA-Platform, отправляются до того, как любой контент-скрипт может выполниться. TLS-отпечаток (JA3/JA4) полностью за пределами возможностей расширения.

Подход 2: JS-инъекция / Stealth-плагины

Stealth-плагины (такие как puppeteer-extra-plugin-stealth) представляют эволюцию подхода расширений. Вместо использования API расширений они внедряют JavaScript через фреймворк автоматизации до загрузки страницы.

Типичный stealth-плагин:

  1. Использует page.evaluateOnNewDocument() или page.addInitScript() для инъекции кода до выполнения любого JavaScript страницы
  2. Переопределяет navigator.webdriver, удаляет объекты, специфичные для фреймворка, патчит navigator.plugins и модифицирует другие обнаружимые свойства
  3. Патчит методы toString(), чтобы переопределенные функции выглядели нативными
  4. Пытается покрыть несколько векторов обнаружения одновременно

Улучшения по сравнению с расширениями:

  • Лучший тайминг: код выполняется до собственных скриптов страницы
  • Может патчить все новые контексты через evaluateOnNewDocument, который применяется к каждому фрейму
  • Может обрабатывать сигналы, специфичные для автоматизации, такие как navigator.webdriver и объекты привязки фреймворка

Оставшиеся слабости:

Граница JavaScript-слоя. Stealth-плагины работают полностью внутри JavaScript. Они могут переопределить то, что возвращают JavaScript API, но не могут контролировать то, что происходит ниже JavaScript-слоя.

Вывод рендеринга Canvas и WebGL. Когда веб-сайт рисует на элементе Canvas и читает пиксельные данные, фактический рендеринг выполняется графическим конвейером движка браузера, а не JavaScript. Stealth-плагин может перехватить toDataURL() и вернуть модифицированные данные, но не может изменить сам рендеринг. Фактический пиксельный вывод остается привязанным к реальному GPU и драйверу, создавая несогласованность между перехваченным ответом API и базовым рендерингом.

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

HTTP и TLS слои. Stealth-плагины не могут модифицировать TLS-рукопожатие (отпечаток JA3/JA4), начальные HTTP-заголовки навигации или Client Hints, отправляемые до выполнения JavaScript.

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

Контексты Worker и SharedWorker. Хотя evaluateOnNewDocument покрывает iframe, Web Worker и SharedWorker создают отдельные JavaScript-контексты, которые могут не получить внедренные скрипты.

Подход 3: Модификация на уровне движка

Модификация на уровне движка изменяет скомпилированный код самого браузера. Вместо перехвата JavaScript API-вызовов после их выполнения, значения устанавливаются у источника, внутри C++ реализации движка браузера. Это подход BotBrowser.

Когда загружается профиль отпечатка, внутренние значения движка браузера настраиваются до создания любого JavaScript-контекста. Когда JavaScript-код вызывает navigator.hardwareConcurrency, он проходит через обычный путь кода движка и возвращает значение профиля через тот же нативный геттер, который использовал бы стандартный браузер. Нет JavaScript-переопределения, нет модифицированного дескриптора свойства, нет измененной цепочки прототипов.

Что обеспечивает контроль на уровне движка:

Нативные дескрипторы свойств. Каждое переопределенное свойство имеет нативный геттер, потому что оно реализовано в C++ коде движка. Object.getOwnPropertyDescriptor возвращает точно то же, что и на стандартном браузере. Вызовы toString() возвращают [native code].

Реальный контроль рендеринга. Canvas, WebGL и аудио-отпечатки не перехватываются на уровне API. Движок рендеринга сам производит вывод, согласованный с загруженным профилем.

Согласованность сетевого уровня. HTTP-заголовки, включая Client Hints, отправляемые при начальной навигации, соответствуют профилю.

Единообразное покрытие контекстов. Каждый JavaScript-контекст, будь то в главном фрейме, iframe, Web Worker, SharedWorker или Service Worker, видит одни и те же значения.

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

Три архитектуры защиты отпечатков Расширение браузера JavaScript сайта Контент-скрипт внедряется здесь JS API слой (пропатчен) Движок рендеринга (не затронут) Сетевой стек (не затронут) TLS слой (не затронут) - Дескрипторы обнаружимы - Свежие контексты не защищены - Нет контроля рендеринга - Нет контроля сетевого уровня - Цепочка прототипов изменена Частичное покрытие JS-инъекция / Stealth-плагин JavaScript сайта evaluateOnNewDocument здесь JS API слой (пропатчен) Движок рендеринга (не затронут) Сетевой стек (частично) TLS слой (не затронут) - Лучший тайминг - Worker-ы могут быть пропущены - Нет контроля рендеринга - Патчи toString() обнаружимы - Несогласованность сигналов Улучшено, но есть пробелы Модификация на уровне движка JavaScript сайта JS API слой (нативные значения) Движок рендеринга (контролируем) Сетевой стек (контролируем) TLS слой (контролируем) - Нативные дескрипторы - Все контексты покрыты - Реальный вывод рендеринга - Полная сетевая согласованность - Нет временного окна Полное покрытие Контролируется защитой Не контролируется (реальные значения) Точка инъекции

Сравнительная таблица

Следующая таблица сравнивает три подхода по ключевым измерениям защиты. Это техническое сравнение, основанное на архитектурных возможностях, а не обзор конкретных продуктов.

Измерение защитыРасширение браузераStealth-плагинУровень движка
Свойства navigatorJS-переопределение (дескриптор обнаружим)JS-переопределение (лучший тайминг)Нативные значения C++
Отпечаток CanvasТолько перехват APIТолько перехват APIВывод рендеринга контролируется
Отпечаток WebGLТолько перехват APIТолько перехват APIВывод рендеринга контролируется
Аудио-отпечатокТолько перехват APIТолько перехват APIВывод обработки контролируется
Отпечаток шрифтовНе контролирует доступностьНе контролирует доступностьСписок шрифтов из профиля
HTTP-заголовкиНе модифицирует начальный запросЧастично (только после навигации)Установлены на уровне сетевого стека
Client HintsНет контроляНет контроляКонтролируются профилем
TLS-отпечаток (JA3/JA4)Нет контроляНет контроляКонтролируется движком
Контексты iframe/WorkerМожет пропустить свежие контекстыПокрывает iframe, может пропустить WorkerВсе контексты единообразны
Проверка дескрипторовОбнаружимо (JS getter)Обнаружимо (JS getter)Нативно (неотличимо)
Целостность цепочки прототиповИзмененаИзмененаНе изменена
Временное окноПосле начала загрузки страницыДо JS страницы, после инициализации движкаНет (активно с запуска процесса)
Кросс-сигнальная согласованностьНезависимые патчиНезависимые патчиУправляется профилем, единая
Накладные расходы производительностиИнъекция скрипта на каждую страницуИнъекция скрипта на каждую страницуНулевые накладные расходы

Подход BotBrowser на уровне движка

BotBrowser реализует защиту отпечатков на уровне движка браузера. Ниже приведен обзор конкретных возможностей, которые обеспечивает эта архитектура.

Система профилей

Каждый отпечаток определяется профилем, захваченным из реальной сессии браузера на реальном оборудовании. Профиль содержит полный набор сигналов устройства: свойства navigator, размеры экрана, информацию о GPU, списки шрифтов, характеристики рендеринга, параметры аудиообработки и многое другое. Загрузка профиля конфигурирует все эти сигналы одновременно, обеспечивая внутреннюю согласованность.

# Загрузка профиля, захваченного из реальной сессии Windows Chrome
chrome --bot-profile="/opt/profiles/windows-chrome-134.enc" \
       --user-data-dir="$(mktemp -d)"

Детерминированное зерно шума

Для исследовательских и тестовых сценариев, требующих воспроизводимых результатов, BotBrowser поддерживает зерно шума, контролирующее все рандомизированные сигналы отпечатков:

chrome --bot-profile="/opt/profiles/profile.enc" \
       --bot-noise-seed=42

Один и тот же профиль с одним и тем же зерном производит идентичные хэши Canvas, вывод WebGL и аудио-отпечатки при каждом запуске, независимо от ОС или оборудования хоста. Это ценно для регрессионного тестирования, CI/CD-конвейеров и контролируемых экспериментов.

Отпечатки по контексту

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

С конфигурацией по контексту один экземпляр браузера может запускать 50 независимых идентичностей. Данные бенчмарков показывают, что этот подход обеспечивает 29% экономии памяти и 57% сокращения количества процессов по сравнению с запуском 50 отдельных экземпляров браузера.

Кроссплатформенная согласованность

Профиль Windows, загруженный на Linux-сервере, производит Windows-согласованный вывод по каждому сигналу: свойства navigator, списки шрифтов, рендеринг Canvas, вывод WebGL, HTTP-заголовки и Client Hints. ОС хоста невидима. Это архитектурно невозможно с подходами расширений или JS-инъекций, поскольку они не могут контролировать движок рендеринга или сетевой стек.

Производительность

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

Результаты бенчмарков:

  • Speedometer 3.0: BotBrowser набирает 42.7 против 42.8 у стандартного Chrome (разница 0.2%)
  • Задержка API Canvas/WebGL/Navigator/Screen/Font: 0 мс дополнительной задержки
  • Нет затрат на инъекцию по страницам: в отличие от расширений и stealth-плагинов, нет JavaScript для инъекции и выполнения при каждой загрузке страницы

Примеры интеграции

Интеграция с Playwright

const { chromium } = require('playwright-core');

(async () => {
  const browser = await chromium.launch({
    executablePath: '/opt/botbrowser/chrome',
    args: [
      '--bot-profile=/opt/profiles/profile.enc',
    ],
    headless: true,
  });

  const context = await browser.newContext({ viewport: null });
  const page = await context.newPage();

  await page.goto('https://abrahamjuliot.github.io/creepjs/');

  // Все сигналы отпечатков согласованы с загруженным профилем.
  // Stealth-плагины не нужны. Патчи evaluateOnNewDocument не нужны.
  // Canvas, WebGL, аудио, шрифты, navigator - все контролируется на уровне движка.

  const fingerprint = await page.evaluate(() => ({
    platform: navigator.platform,
    hardwareConcurrency: navigator.hardwareConcurrency,
    webdriver: navigator.webdriver,
    descriptorType: typeof Object.getOwnPropertyDescriptor(
      Navigator.prototype, 'hardwareConcurrency'
    ).get,
  }));

  console.log(fingerprint);
  await browser.close();
})();

Интеграция с Puppeteer

const puppeteer = require('puppeteer-core');

(async () => {
  const browser = await puppeteer.launch({
    executablePath: '/opt/botbrowser/chrome',
    args: [
      '--bot-profile=/opt/profiles/profile.enc',
      '--bot-disable-console-message',
    ],
    headless: true,
    defaultViewport: null, // Сохранить размеры экрана профиля
  });

  const page = await browser.newPage();
  await page.goto('https://example.com');

  // Проверка согласованности: главный фрейм и iframe возвращают одинаковые значения
  const consistency = await page.evaluate(() => {
    const iframe = document.createElement('iframe');
    iframe.srcdoc = '<html></html>';
    document.body.appendChild(iframe);

    return {
      mainPlatform: navigator.platform,
      iframePlatform: iframe.contentWindow.navigator.platform,
      match: navigator.platform === iframe.contentWindow.navigator.platform,
    };
  });

  console.log('Кросс-контекстная согласованность:', consistency);
  // match: true (гарантируется контролем на уровне движка)

  await browser.close();
})();

Производственная конфигурация

chrome \
  --bot-profile="/opt/profiles/windows-chrome-134.enc" \
  --proxy-server=socks5://user:pass@proxy.example.com:1080 \
  --bot-disable-debugger \
  --bot-disable-console-message \
  --bot-always-active \
  --bot-inject-random-history \
  --bot-port-protection \
  --user-data-dir="/data/session-1" \
  --headless

Верификация: как проверить вашу защиту

Независимо от используемого подхода, вы должны проверить эффективность вашей защиты. Вот ключевые проверки:

1. Проверка дескрипторов свойств

const checks = await page.evaluate(() => {
  const props = [
    'hardwareConcurrency', 'deviceMemory', 'platform',
    'languages', 'webdriver'
  ];

  return props.map(prop => {
    const desc = Object.getOwnPropertyDescriptor(Navigator.prototype, prop);
    return {
      property: prop,
      hasNativeGetter: desc?.get?.toString().includes('[native code]') ?? 'no getter',
      value: navigator[prop],
    };
  });
});
console.log('Проверка дескрипторов:', checks);
// Уровень движка: все показывают [native code]
// Расширение/stealth: показывает JavaScript-функцию

2. Кросс-контекстная согласованность

const crossContext = await page.evaluate(() => {
  const iframe = document.createElement('iframe');
  iframe.style.display = 'none';
  document.body.appendChild(iframe);

  const signals = ['platform', 'hardwareConcurrency', 'deviceMemory', 'languages'];
  const results = {};

  for (const signal of signals) {
    const mainValue = JSON.stringify(navigator[signal]);
    const iframeValue = JSON.stringify(iframe.contentWindow.navigator[signal]);
    results[signal] = {
      main: mainValue,
      iframe: iframeValue,
      consistent: mainValue === iframeValue,
    };
  }

  document.body.removeChild(iframe);
  return results;
});
console.log('Кросс-контекст:', crossContext);

3. Онлайн-инструменты верификации

Перейдите на эти сайты и проверьте наличие предупреждений о несогласованности:

  • CreepJS - Комплексный анализ отпечатков с обнаружением подмен
  • BrowserLeaks - Тестирование отдельных сигналов (Canvas, WebGL, шрифты и т.д.)

Часто задаваемые вопросы

Могут ли stealth-плагины достичь того же результата, что и модификация на уровне движка?

Нет. Stealth-плагины работают на JavaScript-уровне и не могут контролировать вывод рендеринга, заголовки сетевого уровня, TLS-отпечатки или поведение дескрипторов свойств переопределенных значений. Это архитектурные ограничения, а не пробелы в реализации, которые можно исправить лучшим кодом.

Нужны ли мне stealth-плагины при использовании BotBrowser?

Нет. Stealth-плагины избыточны с BotBrowser и могут вносить собственные обнаружимые артефакты (внедренный JavaScript сам может быть обнаружен). BotBrowser обрабатывает все сигналы, которые адресуют stealth-плагины, плюс сигналы, которых они не могут достичь.

Что насчет расширений браузера, которые утверждают, что рандомизируют Canvas?

Рандомизация Canvas на уровне расширения перехватывает вызовы API toDataURL() и getImageData() и добавляет шум к выводу. У этого подхода две проблемы. Во-первых, шум не применяется к фактическому рендерингу, поэтому базовый пиксельный вывод остается неизменным независимо от перехвата API. Во-вторых, случайный шум производит отпечаток, который меняется при каждой загрузке страницы, что само по себе является идентифицирующим сигналом. Реальные устройства производят согласованный Canvas-вывод.

Влияет ли модификация на уровне движка на производительность браузера?

Незначительно. Оценка BotBrowser в Speedometer 3.0 составляет 42.7 против 42.8 у стандартного Chrome, разница 0.2%. Нет затрат на инъекцию по страницам и нет перехвата API во время выполнения. Значения профиля являются частью обычного пути выполнения браузера.

Как BotBrowser обрабатывает новые техники fingerprinting?

Поскольку BotBrowser работает на уровне движка, новые векторы fingerprinting, читающие значения из движка браузера (новые API, новые техники рендеринга, новые типы заголовков), могут быть адресованы обновлением кода движка. Это отличается от подхода расширений/плагинов, где каждый новый вектор требует нового JavaScript-патча, который может иметь собственные проблемы с обнаружимостью.

Могу ли я использовать BotBrowser с Selenium?

BotBrowser лучше всего работает с Playwright и Puppeteer, которые общаются через CDP (Chrome DevTools Protocol). Selenium использует протокол WebDriver, который может вносить дополнительные сигналы автоматизации. Если вы используете Selenium, BotBrowser все равно контролирует сигналы отпечатков на уровне движка, но некоторые артефакты, специфичные для Selenium, могут не обрабатываться.

Сложнее ли настроить модификацию на уровне движка по сравнению со stealth-плагинами?

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

Резюме

Архитектура вашей защиты отпечатков определяет ее эффективность. Расширения браузера и stealth-плагины работают на уровне JavaScript API, что означает, что они могут только перехватывать API-вызовы, но не контролировать базовые сигналы. Модификация на уровне движка контролирует сигналы у их источника, производя согласованный, аутентичный вывод на каждом API, в каждом контексте и на каждом уровне браузерного стека.

BotBrowser имеет открытый исходный код и доступен на GitHub: https://github.com/botswin/BotBrowser

Связанные статьи

#fingerprint protection#browser engine#stealth plugin#browser extension#privacy#architecture comparison#consistency#puppeteer#playwright