Фото George Prentzas на Unsplash

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

Одна из таких ситуаций — скрейпинг вашего сайта неким пользователем. Применение недостаточно эффективных методов идентификации, таких как проверка User-Agent или IP пользователя, оставляют злоумышленнику шанс легко изменить эти данные и остаться необнаруженным.

В то же время использование технологии браузерного фингерпринтинга (дословно «обнаружения отпечатков пальцев», или идентификации браузера) значительно усложняет работу скрейпера, поскольку позволяет обнаружить информацию, которую он не сможет подделать.

В этой статье я расскажу о наиболее часто используемых методах получения так называемого «браузерного фингерпринта» («отпечатка браузера»), а затем покажу, как реализовать один из них.

Действенные методы браузерного фингерпринтинга 

Фингерпринт, как бы странно это ни прозвучало, — не что иное, как хэш, создаваемый на основе определенного набора входных данных.

Весь фокус заключается в том, как его получить.

Использование элемента Canvas

Один из способов получить фингерпринт — нарисовать что-то так, чтобы пользователь этого не видел, и проверить результат.

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

Данный метод требует учета нескольких факторов:

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

После рендеринга изображение нужно превратить в хэш (уникальное значение) — и фингерпринт готов.

Чем этот метод наиболее примечателен? Элемент Canvas можно скрыть, так что пользователь не будет знать, что вы делаете.

Список медиа-устройств

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

Как это сделать? Довольно просто — во всех современных браузерах есть Media Device API, позволяющий получить список всех подключенных устройств.

Следующий код выведет список всех медиа-устройств из вашей системы:

if (!navigator.mediaDevices?.enumerateDevices) {
console.log("enumerateDevices() not supported.");
} else {
// Перечисление камер и микрофонов.
navigator.mediaDevices.enumerateDevices()
.then((devices) => {
devices.forEach((device) => {
console.log(device)
console.log(`${device.kind}: ${device.label} id = ${device.deviceId}`);
});
})
.catch((err) => {
console.error(`${err.name}: ${err.message}`);
});
}

Код был взят из официальной документации MDN. Если запустить его на Firefox, получим такой вывод:

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

Фингерпринтинг аудиоволн

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

Тут фишка в сложности Web Audio API, где задействовано множество математических операций, включая вычисления с плавающей точкой. А это означает, что каждый браузер имеет несколько отличающуюся реализацию в зависимости от того, как написан его код и для какой ОС он предназначен. Эти различия настолько существенны, что позволяют сгенерировать уникальное значение, которое будет неизменным во всех исполнениях.

Иными словами, значение, являясь уникальным для идентификации браузера и ОС, будет постоянным при вычислении его на одной и той же комбинации «браузер + ОС».

Этого достаточно, чтобы использовать полученное значение в качестве надежного фингерпринта, тем более что оно остается неизменным даже при использовании в браузере режима «Инкогнито».

Реализация собственного фингерпринт-кода

Теперь, когда вы ознакомились с наиболее распространенными методами фингерпринтинга, рассмотрим пример реализации одного из них.

Вы уже видели код для создания списка медиа-устройств. Теперь сосредоточимся на использовании элемента Canvas. На мой взгляд, это полезнее, чем сбор большого количества точек данных.

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

Все, что нужно сделать для этого, — определить Canvas, нарисовать в нем несколько фигур, добавить текст, а затем вызвать метод toDataURL. По умолчанию он превратит изображение в PNG, по которому затем проведем итерацию и превратим в единое хэш-значение.

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

function draw(id) {
const canvas = document.getElementById(id);
const ctx = canvas.getContext("2d");

ctx.fillStyle = "rgb(255,0,255)";
ctx.beginPath();
ctx.rect(20, 20, 150, 100);
ctx.fill();
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.fillStyle = "rgb(0,255,255)";
ctx.arc(50, 50, 50, 0, Math.PI * 2, true);
ctx.fill();
ctx.stroke();
ctx.closePath();

txt = 'the brown fox does something with a Z and 908123';
ctx.textBaseline = "top";
ctx.font = '17px "Arial 17"';
ctx.textBaseline = "alphabetic";
ctx.fillStyle = "rgb(255,5,5)";
ctx.rotate(.03);
ctx.fillText(txt, 4, 17);
ctx.fillStyle = "rgb(155,255,5)";
ctx.shadowBlur=8;
ctx.shadowColor="red";
ctx.fillRect(20,12,100,5);

return canvas;
}

function getHash(canvas) {

// функция хэширования
let src = canvas.toDataURL();
let hash = 0;

for (i = 0; i < src.length; i++) {
let char = src.charCodeAt(i);
hash = ((hash<<5)-hash)+char;
hash = hash & hash;
}

return hash
}


const canvas = draw("myCanvas")
console.log(getHash(canvas)) //получение фактического значение хэша, которое нам нужно

Оригинальный код взят отсюда и изменен.

Этот код генерирует такой вывод:

Конечно, изображение получается довольно уродливое, но в данном случае нас волнует не эстетика, а исключительно хэш. Обратите внимание: значение одинаково для Chrome и Chrome в режиме «Инкогнито», но различно для Firefox.

Это очень важно, поскольку означает, что вы сможете узнать, когда краулер/скрейпер пытается «обмануть» вас, используя режим «Инкогнито» в своем браузере.

Конечно, он может сменить браузер, и тогда вы ничего не узнаете, но скрейперы/краулеры обычно всегда используют один и тот же браузер, который пытается идентифицировать себя как что-то другое (с помощью строки User Agent).

В этой ситуации вы можете даже защититься от безголового браузера, пытающегося производить краулинг/скрейпинг вашего сайта. Просто создайте скрипт, который будет выполнять приведенный выше код на скрытом элементе Canvas, получит хэш и сравнит его со значениями, внесенными в ваш собственный «черный список». Если они там есть, то вы знаете, что делать!

Заключение

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

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

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Fernando Doglio: Secure Your Site Against Web Scrapers 

Предыдущая статьяC++: полное руководство по explicit