Компании, отслеживающие действия пользователей в интернете, нуждаются в надёжной идентификации каждого человека без его ведома. Фингерпринтинг через браузер подходит идеально. Никто не заметит, если веб-страница попросит отрисовать фрагмент графики через 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



Как получить отпечаток, пошаговое руководство:

  1. Во-первых, нужно создать массив для хранения значений частоты.

     let freq_data = [];

  2. Затем создаётся объект 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

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

     // 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);

  4. Используя 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);
      }; 

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

     // 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, онлайн-форма деактивирована после публикации статьи





СПЕЦИАЛЬНЫЕ УСЛОВИЯ на PKI-решения для малого и среднего бизнеса до 30.11.2019 г. по промо-коду AL003HRFR. Предложение действует для новых клиентов. Подробности уточняйте у менеджеров +7 (499) 678 2210, sales-ru@globalsign.com.

Комментарии (10)


  1. kolabaister
    13.11.2019 00:17
    +1

    Еще одно обязательно расширение в копилку, спасибо. В рунете, оказывается, тоже многие этим балуются. Например, уведомление на авито сработало.


  1. grishkaa
    13.11.2019 00:44
    +1

    А потом у меня люди спрашивают зачем я отключаю JS на некоторых сайтах


    1. aaklftw
      13.11.2019 19:08

      Большинство сайтов с выключенным JS просто не будут работать.
      Выключать JS не обязательно, достаточно установить расширения Adblock Plus, uBlock Origin или Ghostery, которые блокируют отслеживающие скрипты на сайтах.


      1. grishkaa
        13.11.2019 19:10

        uBlock Origin, конечно, и так стоит. Но отключение JS на сайтах со статьями обычно ломает как раз только самые раздражающие их части — например, видео с автоплеем и попапы про рассылку.

        p.s. те, кто считает, что lazy loading картинок — хорошая идея, должны гореть в аду


  1. vitaliy2
    13.11.2019 11:44

    Тест фингерпринта в статье неидеален. У меня стоит ru, en, ja, он посчитал только ru и вывел всего лишь 5.44 бита. Думаю, что в такой комбинации будет около 17+ бит (en не учитывал, т.?к. он итак обычно стоит по умолчанию). Хотя посчитан HTTP_ACCEPT Headers, в который языки входят, правда насчитал всего 17 бит.

    Также не указывается пересечение, разве что додумывать самому. Например, на ru пишет 5.44 бита, на UTC+3 — 4.54. Но у очень многих с языком ru часовой пояс будет +3, поэтому сумма будет лишь немного выше 5.44 бит.

    Не посчитан fresh rate. Хотя он может меняться (на FreeSync при воспроизведении видео ещё пока не знаю, вероятно, там необязательно менять частоту монитора, а можно сразу выбросить кадры в нужный момент без изменения частоты).

    Не посчитана высота и ширина страницы, но она может часто меняться.

    В остальном тест прикольный.


  1. aaklftw
    13.11.2019 12:05

    Windows используют 70% пользователей и Google Chrome 60%. Размер экрана у 20% пользователей Full HD, в user-agent уже включена ОС и у большинства пользователей user-agent будет одинаков. В территориально маленьких странах часовой пояс будет совпадать с языком в системе.
    В Chrome и Firefox установлено от трёх до пяти стандартных плагинов, такие как Chrome PDF Plugin и Native Client. Доступ к установленным расширениям получить нельзя.
    Если вы проверите аудио отпечаток на нескольких одинаковых серверах, то он будет одинаковым, по крайней мере на windows 10. Шрифты в ОС и браузерах одинаковые.
    Из всех параметров которые мы написали можно идентифицировать пользователя только по canvas, и то затруднительно.


    1. Mel
      13.11.2019 16:06

      > Шрифты в ОС и браузерах одинаковые.
      а если ставил кастомные?


  1. paulvales
    13.11.2019 12:05

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


  1. GaDzik
    13.11.2019 12:05

    Крутяк.


  1. Darkhon
    13.11.2019 20:26
    +1

    В Firefox сейчас расширение CanvasBlocker стало универсальным инструментом: искажает отпечатки Canvas, WebGL, Audio, DOMRect API…