Перейти к содержимому

Как считается device fingerprint вручную: разбор canvas, WebGL и audio-хэшей на дампе сессии

Как считается device fingerprint вручную: разбор canvas, WebGL и audio-хэшей на дампе сессии
Время чтения: 12 мин.

В дампе браузерных сессий маркетплейса — 47 регистраций за сутки. Каждый раз новый email и чистые cookie. IP менялся через пул резидентных прокси. Но одно значение совпадало во всех 47 записях: компактный хэш — canvas fingerprint. WebGL renderer string и audio fingerprint тоже совпадали. Злоумышленник аккуратно ротировал cookie и IP, но забыл про отпечаток устройства. Одно правило в антифрод-системе склеило все аккаунты в кластер за секунды.

Чтобы писать такие правила и разбирать подобные дампы, нужно понимать, как device fingerprint считается вручную — на уровне конкретных JS-вызовов, пиксельных расхождений и чисел с плавающей точкой. Эта статья — про внутреннюю механику трёх главных техник: canvas, WebGL и audio. С кодом, который можно запустить прямо в DevTools.

Device fingerprint в антифроде: зачем разбирать вручную

Device fingerprint (отпечаток устройства) — набор характеристик браузера и железа, которые дают уникальный идентификатор. В отличие от cookie (небольших файлов, которые браузер хранит локально), fingerprint не сохраняется на устройстве: он вычисляется заново при каждом визите. Стереть его нельзя — можно только изменить параметры, из которых он собирается.

Исследование Englehardt & Narayanan («Online Tracking: A 1-million-site Measurement», 2016) обнаружило canvas fingerprinting примерно на 5% сайтов из топ-1M. С тех пор доля росла. Cookie блокируются, удаляются и ограничиваются законодательно (GDPR), а fingerprint продолжает работать — он зависит от железа, а не от хранилища браузера.

В антифроде уникальный отпечаток устройства — якорь идентификации. Cookie чистятся за секунду. IP меняется через прокси. Но пересобрать fingerprint сложнее: он привязан к GPU, звуковой подсистеме, шрифтовым таблицам ОС.

Место fingerprint-анализа в цепочке антифрод-детекции

Зачем злоумышленнику мультиаккаунтинг? Десятки учётных записей — это злоупотребление промо-акциями, обход лимитов, отмыв средств через кардинг или бот-регистрации. Цепочка детекции выглядит так:

  1. Запрос пользователя — браузер открывает страницу
  2. Сбор сигналов — JS-скрипт вычисляет canvas, WebGL, audio и десятки других параметров
  3. Вычисление fingerprint — сигналы хэшируются и объединяются
  4. Сравнение с базой — fingerprint сопоставляется с известными профилями
  5. Решение — allow / challenge (капча) / block

Ручной антифрод анализ браузерной сессии нужен на этапах 4–5: система сработала по fingerprint-правилу, и аналитик проверяет — это реальный мультиаккаунтинг или ложное срабатывание. Коллизия (два разных устройства случайно дали одинаковый хэш) — штука нередкая, особенно в корпоративных сетях. Без понимания внутренней механики каждого хэша невозможно ни тюнить пороги срабатывания, ни оценивать, что именно спуфит антидетект-браузер (специальное ПО для подмены параметров fingerprint).

Три техники вносят максимальный вклад в уникальность: canvas fingerprinting, WebGL-хэш и audio fingerprint. Разберём алгоритм каждой — от JS-кода до конечного числа.

Canvas fingerprinting: как работает browser fingerprinting алгоритм

Canvas — HTML-элемент для отрисовки 2D-графики через JavaScript. Идея canvas fingerprinting: одна и та же команда рисования даёт пиксельно-разный результат на разном железе. Причина — различия в GPU, графических драйверах, шрифтовых движках ОС и настройках сглаживания (anti-aliasing — технология смягчения пиксельных «лесенок» на контурах букв и линий).

Как считается canvas hash по шагам

  1. Создание невидимого холста. Скрипт создаёт элемент <canvas> в памяти — пользователь его не видит.
  2. Рисование. На холст наносят текст заданным шрифтом, цветные прямоугольники, градиенты. Задача — задействовать максимум путей рендеринга, чтобы увеличить различия между устройствами. Часто используют фразы с полным алфавитом вроде «Cwm fjordbank glyphs mute quiz» — так задействуется больше глифов (форм букв).
  3. Чтение пикселей. Метод canvas.toDataURL() экспортирует содержимое холста в строку Base64 — текстовое представление PNG-картинки.
  4. Хэширование. Строка прогоняется через хэш-функцию (например, djb2 или MurmurHash). На выходе — короткое число или hex-строка: это и есть canvas hash.

Предусловия и ограничения. Работает, если браузер поддерживает Canvas API (все современные). Не работает или даёт зашумлённый результат, если: Firefox с включённым privacy.resistFingerprinting, Brave с активными Shields, Tor Browser — они намеренно добавляют случайные искажения в рендеринг (по данным ThumbmarkJS).

Минимальный код для консоли DevTools (адаптировано из Octo Browser Blog):

let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 200; canvas.height = 50;
ctx.textBaseline = "top";
ctx.font = "20px Arial";
ctx.fillStyle = "#f60";
ctx.fillText("Fingerprint", 10, 10);
let data = canvas.toDataURL();
let hash = 0;
for (let i = 0; i < data.length; i++) { hash = (hash << 5) - hash + data.charCodeAt(i); hash |= 0; }
console.log("Canvas hash:", hash);

Что тут происходит построчно:

  • Строки 1–3: создаётся холст 200×50 пикселей, нигде не отображается.
  • Строки 4–7: рисуется слово «Fingerprint» оранжевым цветом (#f60), шрифтом Arial 20px. Тут и проявляются различия — каждая ОС по-своему рендерит букву «F».
  • Строка 8: toDataURL() экспортирует картинку в Base64-строку (начинается с data:image/png;base64,...).
  • Строки 9–10: простейшая хэш-функция djb2 перебирает каждый символ строки и складывает числа. Операция hash |= 0 обрезает результат до 32 бит.

Ожидаемый результат: в консоли появится Canvas hash: и число вроде -1283128615. На другом устройстве (или даже в другом браузере на том же ПК) число будет другим.

Почему пиксели отличаются, если текст выглядит одинаково? Хинтинг шрифтов (подгонка контуров букв под пиксельную сетку) зависит от шрифтового движка: Windows DirectWrite, macOS Core Text и Linux FreeType обрабатывают букву «F» по-разному. К этому добавляются различия в GPU-драйверах: один сглаживает край глифа одним способом, другой — другим. На пикселе [124, 37] значение RGB отличается на единицу — визуально незаметно, но для хэша этого достаточно.

Исследование Mowery & Shacham (2012, «Pixel Perfect: Fingerprinting Canvas in HTML5») показало, что canvas добавляет около 5,7 бита энтропии. Этого хватает, чтобы в комбинации с другими сигналами идентифицировать устройство среди миллионов.

WebGL хэш браузера: renderer string и GPU-зависимый рендеринг

WebGL (Web Graphics Library) — браузерный API для 3D-графики. Для device fingerprint антифрод-систем он ценен двумя сигналами: текстовой строкой с моделью GPU и хэшем отрисованной 3D-сцены.

Renderer string: модель видеокарты в открытом виде

Расширение WEBGL_debug_renderer_info возвращает vendor (производитель) и renderer (модель GPU). Три строки JS в DevTools — и вы видите, какая видеокарта стоит на машине:

const gl = document.createElement("canvas").getContext("webgl");
const ext = gl.getExtension("WEBGL_debug_renderer_info");
console.log(gl.getParameter(ext.UNMASKED_RENDERER_WEBGL));

Ожидаемый результат: строка вроде ANGLE (NVIDIA GeForce RTX 3070 Direct3D11 vs_5_0 ps_5_0) на Windows или ANGLE (Apple M2, APPLE M2, OpenGL 4.1) на macOS (примеры из документации ThumbmarkJS). Строка стабильна — железо не меняется между визитами. WebGL renderer string — один из самых устойчивых компонентов fingerprint.

Предусловия и ограничения. Работает, если WebGL включён и расширение доступно. В Firefox расширение WEBGL_debug_renderer_info заблокировано по умолчанию — браузер возвращает null. Все профили Chrome на одной машине дают идентичный renderer string: WebGL renderer string считывается напрямую с GPU-драйвера через ANGLE (Abstraction Layer — прослойка между браузером и графическим API ОС), значение не зависит от профиля браузера. В некоторых конфигурациях Chrome (корпоративные политики, определённые версии 100+) UNMASKED_RENDERER_WEBGL может возвращать обобщённую ANGLE-строку вместо точной модели GPU. Разделить профили по этому параметру невозможно.

Хэш 3D-рендеринга

Помимо текстовой строки, WebGL позволяет отрисовать 3D-сцену и прочитать пиксели — аналогично canvas fingerprinting, но через 3D-конвейер. Различия в шейдерной компиляции (преобразовании кода шейдеров в инструкции конкретного GPU) и GPU-драйверах дают разные пиксели на разных видеокартах. В продакшн-системах (например, Fingerprint.com — по их данным, технология собирает более 100 сигналов) комбинируют renderer string с хэшем 3D-рендеринга для повышения уникальности.

Audio fingerprint браузера: как звуковой сигнал становится идентификатором

Web Audio API — браузерный интерфейс для обработки звука. Audio fingerprinting использует его побочный эффект: одна и та же операция над звуковым сигналом даёт чуть разные числовые значения на разном железе и ОС. Пользователь ничего не слышит — всё происходит в памяти.

Алгоритм audio fingerprint по шагам

  1. Виртуальный звуковой контекст. Скрипт создаёт OfflineAudioContext — обработчик, который рендерит звук в памяти без вывода на динамики.
  2. Генерация тона. OscillatorNode (генератор колебаний) создаёт треугольную волну на заданной частоте (например, 10 000 Гц).
  3. Обработка. Сигнал проходит через DynamicsCompressorNode — компрессор, «сжимающий» амплитуду волны. Параметры threshold, ratio, release time влияют на форму выходного сигнала. Тут и появляются аппаратно-зависимые различия.
  4. Чтение буфера. После рендеринга получаем массив float-значений — семплы (отсчёты звука). При частоте 44 100 Гц и буфере в 1 секунду — 44 100 чисел.
  5. Вычисление fingerprint. Из середины буфера (чтобы избежать краевых эффектов) берётся срез семплов, суммируются их абсолютные значения. Результат — одно число.

Рабочий код для DevTools (адаптировано из ThumbmarkJS):

const ctx = new OfflineAudioContext(1, 44100, 44100);
const osc = ctx.createOscillator();
const comp = ctx.createDynamicsCompressor();
osc.type = "triangle";
osc.frequency.value = 10000;
osc.connect(comp);
comp.connect(ctx.destination);
osc.start(0);
const buf = await ctx.startRendering();
console.log(buf.getChannelData(0).slice(4500, 5000).reduce((a, v) => a + Math.abs(v), 0));
  • Строки 1–5: создаётся контекст (1 канал, 44100 семплов), осциллятор настраивается на треугольную волну 10 кГц.
  • Строки 6–8: маршрут сигнала: осциллятор → компрессор → выход; осциллятор запускается.
  • Строка 9: startRendering() обрабатывает весь буфер в памяти. Используется await, потому что рендеринг — асинхронная операция; в консоли Chrome top-level await работает из коробки.
  • Строка 10: из буфера берётся срез индексов 4500–5000 (середина — стабильный участок), суммируются абсолютные значения.

Ожидаемый результат: число вроде 35.74996137619019 (конкретное значение зависит от версии браузера, ОС и аудио-стека — у вас будет другое). Повторный запуск на том же устройстве и той же версии браузера даёт то же число. Для повторного запуска оберните код в блок { ... } или используйте let вместо const, иначе консоль выдаст SyntaxError о повторном объявлении. На другом устройстве — отличие после нескольких знаков. При обновлении версии браузера значение тоже может измениться.

Предусловия и ограничения. Работает в Chrome, Edge, Opera. Firefox с включённым privacy.resistFingerprinting добавляет шум в данные AudioBuffer — audio fingerprint становится нестабильным между запусками (сам API при этом не блокируется). Safari ограничивает точность выходных значений (по данным ThumbmarkJS). На виртуальных машинах audio fingerprint часто даёт коллизии — виртуализированная звуковая подсистема у многих VM одинаковая. Типичная ситуация с VMware и VirtualBox.

Анализ дампа браузерной сессии: делай раз, делай два, делай три

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

Требования к окружению

  • Браузер: Chrome или Edge актуальной версии (DevTools с поддержкой async/await)
  • Данные: лог сессий из антифрод-системы с полями canvas_hash, webgl_renderer, audio_fp (или сырые JS-дампы). Если антифрод-системы нет — воспроизведите на двух устройствах
  • RAM: хватит любого ПК, на котором запускается Chrome
  • ОС: Windows, macOS или Linux — без ограничений
  • Сеть: не требуется (все операции локальные)

Шаг 1. Снять fingerprint-набор с исследуемого устройства

Откройте DevTools (F12), вкладку Console. Последовательно выполните три блока кода из разделов выше: canvas, WebGL, audio. Запишите три значения.

Как проверить: canvas — в консоли появится число (32-бит); WebGL — строка с моделью GPU; audio — float-число. Если вместо значения — ошибка TypeError или null, браузер блокирует соответствующий API (см. ограничения каждой техники выше).

Шаг 2. Сопоставить с дампом подозрительных сессий

Берёте fingerprint-данные из антифрод-лога и сравниваете по трём параметрам:

Параметр Сессия 1 Сессия 47 Совпадение
canvas_hash -1283128615 -1283128615 Да
webgl_renderer ANGLE (Intel UHD 630…) ANGLE (Intel UHD 630…) Да
audio_fp 35.74996137 35.74996137 Да

Шаг 3. Интерпретировать результат

  • Все три совпадают — с высокой вероятностью сессии выполнены с одного устройства. Мультиаккаунтинг.
  • Canvas и audio совпадают, webgl_renderer различается — возможен антидетект-браузер, спуфящий GPU-строку. Дополнительно проверьте User-Agent и разрешение экрана.
  • Только canvas совпадает, audio и WebGL различаются — скорее коллизия (совпадение хэшей у разных устройств), а не один злоумышленник. Особенно вероятно в корпоративной среде с одинаковыми ОС-образами.

Сравнение fingerprinting-техник: что даёт максимум энтропии

Техника Энтропия Стабильность Сложность спуфинга Когда не работает
Canvas hash Высокая (~5,7 бит) Высокая — меняется при обновлении GPU-драйвера Средняя — антидетект-браузеры подменяют рендеринг Firefox resistFingerprinting, Brave Shields, Tor
WebGL renderer string Средняя Очень высокая — не меняется до замены GPU Низкая — строка общая для всех профилей Chrome Firefox блокирует расширение по умолчанию
WebGL render hash Высокая Высокая Средняя WebGL отключён, виртуализация GPU
Audio fingerprint Средняя Высокая Средняя Firefox Private Browsing, Safari, VM (коллизии)

Ни один сигнал в одиночку не даёт достаточной точности для антифрод-решения. Связка canvas + audio + WebGL renderer — минимальный набор для надёжной привязки. В продакшне добавляют ещё десятки сигналов: шрифты, разрешение экрана, часовой пояс, список плагинов.

Когда fingerprint — слабый фактор: на мобильных устройствах уникальность ниже (ряд исследований подтверждает, что уникальность fingerprint на смартфонах существенно ниже, чем на десктопе). На виртуальных машинах audio часто коллизирует. В корпоративных сетях с унифицированными образами canvas-хэши сотен машин совпадают.

Где спуфинг fingerprint ломается на практике

Антидетект-браузеры (Multilogin, GoLogin, Octo Browser) создают «виртуальные профили» с разными отпечатками для каждого аккаунта. На бумаге звучит солидно. На практике спуфинг ломается в нескольких точках.

Несогласованность между сигналами. WebGL renderer string заявляет NVIDIA RTX 4090, а canvas рендерится как на интегрированной Intel HD — антидетект подменил строку, но не подменил GPU-конвейер. По данным Proxies.sx, ML-системы (Cloudflare, PerimeterX) детектируют такие несогласованности как один из первых триггеров.

Статичный шум canvas. Если антидетект добавляет один и тот же «зашумлённый» canvas при каждом запросе, антифрод-система фиксирует аномально стабильный noise pattern. ML-модели уже обучены различать естественный рендеринг и синтетический шум (по данным того же источника).

Audio на виртуалках. Антидетект внутри VM часто даёт audio fingerprint, характерный для конкретного гипервизора. Антифрод-система, видя audio_fp, типичный для VMware + Windows + Chrome, повышает risk score. Поэтому антидетект-браузеры рекомендуют запускать на физических машинах.

Иллюзия Chrome-профилей. Все профили Chrome на одной машине дают идентичный canvas, WebGL и audio fingerprint. Злоумышленник, рассчитывавший, что «новый профиль = новый отпечаток», ошибается. Для реального разделения нужен антидетект-браузер или отдельная физическая/виртуальная машина.

Практический вывод для антифрод-аналитика: при разборе дампа ищите inconsistencies между компонентами. Canvas «говорит» одно, WebGL — другое? Маркер спуфинга. Audio одинаков у сотен сессий? Проверяйте метаданные VM.

Большинство антифрод-команд обращаются с device fingerprint как с чёрным ящиком: хэш совпал — блокируем, не совпал — пропускаем. Но без понимания того, что внутри, невозможно отличить реальный мультиаккаунтинг от коллизии canvas-хэшей на корпоративных ПК с одинаковым образом Windows. Невозможно объяснить, почему после обновления GPU-драйверов у половины пользователей «сменился» fingerprint — и не нужно паниковать. Невозможно поймать грамотно настроенный антидетект-браузер, если аналитик не знает, какие сигналы подменяются, а какие нет.

Ручной разбор canvas, WebGL и audio — не академическое упражнение. Это то, что отделяет аналитика, который пишет правила детекции, от аналитика, который нажимает кнопки в дашборде. В ближайшие год-два ситуация усложнится: WebGPU (новый браузерный API для GPU-вычислений) добавит ещё один вектор fingerprinting, а ML на стороне антифрода научится ловить спуфинг по поведенческим паттернам. Но базовый навык — разложить хэш на составные части и понять, что за каждым числом стоит — останется фундаментом. Если хочешь не просто статьи, а пройти базу ИБ системно для старта в антифроде — IB Basics закрывает фундамент за пару месяцев, без воды.

Эту тему и смежные навыки разбирают на практике в курсе «Антифрод-аналитик» Codeby Academy.