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

В дампе браузерных сессий маркетплейса — 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-анализа в цепочке антифрод-детекции
Зачем злоумышленнику мультиаккаунтинг? Десятки учётных записей — это злоупотребление промо-акциями, обход лимитов, отмыв средств через кардинг или бот-регистрации. Цепочка детекции выглядит так:
- Запрос пользователя — браузер открывает страницу
- Сбор сигналов — JS-скрипт вычисляет canvas, WebGL, audio и десятки других параметров
- Вычисление fingerprint — сигналы хэшируются и объединяются
- Сравнение с базой — fingerprint сопоставляется с известными профилями
- Решение — 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 по шагам
- Создание невидимого холста. Скрипт создаёт элемент
<canvas>в памяти — пользователь его не видит. - Рисование. На холст наносят текст заданным шрифтом, цветные прямоугольники, градиенты. Задача — задействовать максимум путей рендеринга, чтобы увеличить различия между устройствами. Часто используют фразы с полным алфавитом вроде «Cwm fjordbank glyphs mute quiz» — так задействуется больше глифов (форм букв).
- Чтение пикселей. Метод
canvas.toDataURL()экспортирует содержимое холста в строку Base64 — текстовое представление PNG-картинки. - Хэширование. Строка прогоняется через хэш-функцию (например, 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 по шагам
- Виртуальный звуковой контекст. Скрипт создаёт
OfflineAudioContext— обработчик, который рендерит звук в памяти без вывода на динамики. - Генерация тона.
OscillatorNode(генератор колебаний) создаёт треугольную волну на заданной частоте (например, 10 000 Гц). - Обработка. Сигнал проходит через
DynamicsCompressorNode— компрессор, «сжимающий» амплитуду волны. Параметры threshold, ratio, release time влияют на форму выходного сигнала. Тут и появляются аппаратно-зависимые различия. - Чтение буфера. После рендеринга получаем массив float-значений — семплы (отсчёты звука). При частоте 44 100 Гц и буфере в 1 секунду — 44 100 чисел.
- Вычисление 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-levelawaitработает из коробки. - Строка 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.