Компании, отслеживающие действия пользователей в интернете, нуждаются в надёжной идентификации каждого человека без его ведома. Фингерпринтинг через браузер подходит идеально. Никто не заметит, если веб-страница попросит отрисовать фрагмент графики через canvas или сгенерирует звуковой сигнал нулевой громкости, замеряя параметры ответа.
Метод работает по умолчанию во всех браузерах, кроме Tor. Он не требует получения никаких разрешений пользователя.
Тотальный трекинг
Недавно журналистка NY Times Кашмир Хилл обнаружила, что некая малоизвестная компания Sift накопила на неё досье объёмом 400 страниц. Там список покупок за несколько лет, все сообщения хостам на Airbnb, лог запусков кошелька Coinbase на мобильном телефоне, IP-адреса, заказы пиццы с iPhone и многое другое. Аналогичный сбор ведут несколько скоринговых фирм. Они учитывают до 16 000 факторов при составлении «рейтинга доверия» каждого пользователя. Трекеры Sift установлены на 34 000 сайтов и мобильных приложений.
Поскольку следящие куки и скрипты не всегда хорошо работают или отключены у клиента, трекинг пользователей дополняют фингерпринтингом — это набор методов для получения уникального «отпечатка» браузера/системы. Список установленных шрифтов, плагинов, разрешение экрана и другие параметры в сумме дают достаточно бит информации для получения уникального ID. Хорошо работает фингерпринтинг через canvas.
Фингерпринтинг через Canvas API
Веб-страница отправляет браузеру команду отрисовать графический объект из нескольких элементов.
<canvas class="canvas"></canvas>
const canvas = document.querySelector('.canvas');
const ctx = canvas.getContext('2d');
// Maximize performance effect by
// changing blending/composition effect
ctx.globalCompositeOperation = 'lighter';
// Render a blue rectangle
ctx.fillStyle = "rgb(0, 0, 255)";
ctx.fillRect(25,65,100,20);
// Render a black text: "Hello, OpenGenus"
var txt = "Hello, OpenGenus";
ctx.font = "14px 'Arial'";
ctx.fillStyle = "rgb(0, 0, 0)";
ctx.fillText(txt, 25, 110);
// Render arcs: red circle & green half-circle
ctx.fillStyle = 'rgb(0,255,0)';
ctx.beginPath();
ctx.arc(50, 50, 50, 0, Math.PI*3, true);
ctx.closePath();
ctx.fill();
ctx.fillStyle = 'rgb(255,0,0)';
ctx.beginPath();
ctx.arc(100, 50, 50, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
Результат выглядит примерно так:
Функция Canvas API под названием toDataURL() возвращает URI с данными, которые соответствуют этому результату:
console.log(canvas.toDataURL());
/*
Ouputs something like:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNby
mblAAAWDElEQVQImWNgoBMAAABpAAFEI8ARexAAAElFTkSuQmCC"
*/
Этот URI отличается на разных системах. Затем он хэшируется и используется вместе с другими битами данных, которые составляют уникальный отпечаток системы. Среди прочего:
- установленные шрифты (около 4,37 бита идентифицирующей информации);
- установленные плагины в браузере (3,08 бита);
- заголовки HTTP_ACCEPT (16,85 бита);
- user-agent;
- язык;
- часовой пояс;
- размера экрана;
- камера и микрофон;
- версия ОС;
- и др.
Хэш отпечатка canvas добавляет ещё 4,76 бита идентифицирующей информации. Хэш отпечатка WebGL — 4,36 бита.
Тест фингерпринтинга
Недавно в дополнение к набору параметров добавился ещё один: звуковой отпечаток через AudioContext API.
Ещё в 2016 году этот метод идентификации уже использовали сотни сайтов, такие как Expedia, Hotels.com и др.
Фингерпринтинг через AudioContext API
Алгоритм действий такой же: браузер выполняет задачу, а мы записываем результат выполнения и вычисляем уникальный хэш (отпечаток), только в данном случае данные извлекаются из аудиостека. Вместо Canvas API идёт обращение к AudioContext API, это интерфейс Web Audio API, который поддерживают все современные браузеры.
Браузер генерирует низкочастотный аудиосигнал, который обрабатывается с учётом звуковых настроек и оборудования, установленного на устройстве. При этом никакой звук не записывается и не воспроизводится. Колонки и микрофон не задействуются.
Преимущество этого метода фингерпринтинга в том, что он независим от браузера, так что позволяет отслеживать пользователя даже после перехода с Chrome в Firefox, затем в Opera и так далее.
Тест отпечатка через AudioContext API
Как получить отпечаток, пошаговое руководство:
- Во-первых, нужно создать массив для хранения значений частоты.
let freq_data = [];
- Затем создаётся объект AudioContext и различные узлы для генерации сигнала и сбора информации, используя встроенные методы объекта AudioContext.
// Create nodes const ctx = new AudioContext(); // AudioContext Object const oscillator = ctx.createOscillator(); // OscillatorNode const analyser = ctx.createAnalyser(); // AnalyserNode const gain = ctx.createGain(); // GainNode const scriptProcessor = ctx.createScriptProcessor(4096, 1, 1); // ScriptProcessorNode
- Отключаем громкость и соединяем узлы друг с другом.
// Disable volume gain.gain.value = 0; // Connect oscillator output (OscillatorNode) to analyser input oscillator.connect(analyser); // Connect analyser output (AnalyserNode) to scriptProcessor input analyser.connect(scriptProcessor); // Connect scriptProcessor output (ScriptProcessorNode) to gain input scriptProcessor.connect(gain); // Connect gain output (GainNode) to AudioContext destination gain.connect(ctx.destination);
- Используя
ScriptProcessorNode
, создаём функцию, которая собирает частотные данные во время обработки звука.
- Функция создаёт типизированный массив
Float32Array
с длиной, равной числу (частоте) значений данных вAnalyserNode
, а затем заполняет его значениями.
- Эти значения затем копируются в массив, который мы создали ранее (
freq_data
), чтобы мы могли легко записывать их в выходные данные.
- Отключаем узлы и выводим результат.
scriptProcessor.onaudioprocess = function(bins) { // The number of (frequency) data values bins = new Float32Array(analyser.frequencyBinCount); // Fill the Float32Array array of these based on values analyser.getFloatFrequencyData(bins); // Copy frequency data to 'freq_data' array for (var i = 0; i < bins.length; i = i + 1) { freq_data.push(bins[i]); } // Disconnect the nodes from each other analyser.disconnect(); scriptProcessor.disconnect(); gain.disconnect(); // Log output of frequency data console.log(freq_data); };
- Функция создаёт типизированный массив
- Начинаем воспроизводить тон, так что звук генерируется и обрабатывается в соответствии с функцией.
// Start playing tone oscillator.start(0);
/* Output: [ -119.79788967947266, -119.29875891113281, -118.90072674835938, -118.08164726269531, -117.02244567871094, -115.73435120521094, -114.24555969238281, -112.56678771972656, -110.70404089034375, -108.64968109130886, ... ] */
Эта комбинация значений хэшируется для создания отпечатка, который затем используется с другими битами идентификации.
Для защиты от такого трекинга можно использовать расширения вроде AudioContext Fingerprint Defender, которые подмешивают случайный шум в отпечаток.
NY Times приводит адреса email, по которым можно обратиться в трекинговые фирмы и попросить показать собранную на вас информацию.
- Zeta Global: онлайн-форма
- Retail Equation: returnactivityreport@theretailequation.com
- Riskified: privacy@riskified.com
- Kustomer: privacy@kustomer.com
- Sift: privacy@sift.com, онлайн-форма деактивирована после публикации статьи