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

Тот способ основывается на использовании метода DOM элемента getComputedStyle. Будучи вызванным у HTMLAnchorElement, он позволяет различать :visited и обычные состояния ссылок на популярные сайты.

Баг давно закрыли и больше им воспользоваться нельзя.

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

Ниже приведен полный исходный код proof-of-concept реализации этого способа. С его помощью можно продемонстрировать, что вы посещаете сайт habrahabr.ru, но ни разу не были на сайте hornet.com.

var diffTreshold = 200; // Порог времени, который необходимо преодолеть, чтобы считать, что пользователь посетил сайт.
var body = document.querySelector('body');

var testResults = [];
var testCases = [
  'hornet.com', 'habrahabr.ru'
];

testCases.forEach(test);

function test (host) {
  var start = new Date();
  var img = new Image();
  img.src = 'http://'+host+'/favicon.ico';
  img.onload = function () {
    saveResult(host, start, new Date());
  }
  body.appendChild(img);
}

function saveResult (host, start, end) {
  var diff = end - start;
  testResults.push({
    host: host,
    start: start,
    end: end,
    diff: diff,
    visited: diff <= diffTreshold
  });
}



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

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

Этот способ имеет также и один нерешенный мной недостаток: после первого такого теста его последующие запуски сделают результаты бесполезными.

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

UPD:
Удивило, чтот пост стал достоин инвайта. Видимо, это действительно очень интересная тема. Попробую реализовать этот метод в виде готовой библиотеки.

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


  1. Rastishka
    29.05.2015 12:20
    +4

    Идея классная!
    Но не хватает примера с десятком сайтов для теста.


  1. nickolaym
    29.05.2015 12:40
    +3

    Произвольная иконка для страницы

    <link rel="shortcut icon" src="fig-vam-a-ne-favicon.ico" />
    

    Или проверка реферерра при запросе favicon
    RewriteCond %{HTTP_REFERER} ^(?!your.domain.com)(.+)$
    RewriteRule ^favicon.ico$ [R=404,L]
    

    Ибо нефиг!


    1. sonor
      29.05.2015 13:47

      А если стоит блокировщик реферера?
      Но что нефиг согласен.


    1. shergin
      29.05.2015 20:14

      Описанные вами способы защиты работать не будут. Правильным решением было бы использовать Content Security Policy.


    1. Xu4
      02.06.2015 15:31

      Проверкой реферера вы только облегчите работу этому скрипту. Если в браузере в кэше есть «favicon.ico», она нормально отдастся скрипту. Если в кэше её нет, вернётся ошибка 404, которую отловить легче, чем экспериментировать с «threshold».


  1. Ogra
    29.05.2015 13:13
    +20

    var diffTreshold = 200; // Порог времени, который необходимо преодолеть, чтобы считать, что пользователь посетил сайт.
    visited: diff > diffTreshold

    Ну наоборот же, елы-палы. Преодолели порог, значит не посещали сайт. Вы свой froof-of-concept сами хоть проверяли?

    Ну и по мелочи:
    saveResult(host, start, new Date() — забыли закрывающую скобку;
    threshold, а не treshold;


    1. YChebotaev Автор
      29.05.2015 21:22

      Прошу прощения за ошибки и опечатки, сам удивился, как это у меня так получилось.


  1. CAH4A
    29.05.2015 14:06
    +6

    Если сделать достаточное количество запросов, то можно даже попытаться применить кластерный анализ, вместо непонятно что означающего «среднего значения между минимальным и максимальным временем загрузки».

    А ещё, можно делать два запроса: на favicon.ico и на favicon.ico?randomhashhere.
    Тогда можно знать за сколько скачивается та же фавиконка, но без кеша.


    1. YChebotaev Автор
      29.05.2015 21:14

      Я не думаю, что делать свое решение для кластерного анализа будет целесообразно в каждом конкретном случае. Однако, его можно попытаться продать как отдельный сервис или стать частью более крупного решения.


      1. CAH4A
        29.05.2015 22:25

        Тут отлично подойдёт самый простой алгоритм «k-средних» (а у нас тут k=2), да и пишется он на коленке за пол часа.
        И для каждого конкретного случая писать его снова не нужно. В том и прелесть, если сравнению с Threshold=200.

        Продать «k-средних» как отдельный сервис, это к маркетологам. -)


        1. YChebotaev Автор
          29.05.2015 22:52

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

          На моем компьютере, скорость кэшированной картинки редко превышает 10 миллисекунд, а скорость загрузки с сервера редко быстее 100 миллисекунд. Тут нужно еще учитывать, что раз пользователь не был на сайте, то браузер, скорее всего, будет еще и dns lookup делать.


  1. stifff
    29.05.2015 14:32
    +4

    «боливар не выдержит двоих.»
    Такой трюк только один раз можно будет провернуть.


  1. akirsanov
    29.05.2015 16:04
    +2

    Интересно!
    Также есть замечательное исследование тайминг атак с BH-13 media.blackhat.com/us-13/US-13-Stone-Pixel-Perfect-Timing-Attacks-with-HTML5-WP.pdf
    Ребята заметили, что отрисовка элементов занимает некоторое время, увеличили это время с помощью фильтров и выводили среднее время задержки рендеринга для посещенной и непосещенной ссылки. На данный момент затронутая ими проблема пофиксена в ff/chrome/ie.


    1. Bo0oM
      29.05.2015 21:49

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

      В копилку, есть детектор социалок от bushwhackers. Еще прошу обратить внимания на детект посещения с помощью HSTS :)


      1. akirsanov
        30.05.2015 17:13

        Там было и детектирование посещения по времени отрисовки, и распознавание элементов страницы по view-source во фрейме при наложении фильтров, — и все это пофиксено в 13 году.


  1. amarao
    29.05.2015 18:31
    +5

    Для тех, кого раздражают сайты, качающие что-то с других сайтов без вашего разрешения (например, как в этом посте):

    addons.mozilla.org/ru/firefox/addon/requestpolicy

    Куда менее агрессивный, чем noscript, и после минимальной настройки позволяет практически исключить любые подобные трюки.


  1. xana4ok
    30.05.2015 03:29

    Это только половина дела. Надо взять топ 100000 сайтов по версии quantcast с демографией.


  1. bogolt
    30.05.2015 07:40

    Пора кажется пользователям каждый сайт запускать в своей песочнице.


  1. SlimHouse
    02.06.2015 01:04

    <link rel=«icon» type=«image/png» href=«example.com/myicon.png»> вот вам