Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Меня всегда напрягало то, как навязчиво Google AdSense подсовывал контекстную рекламу в зависимости от моих старых запросов в поисковике. Вроде бы и времени с момента поиска прошло достаточно много, да и куки и кеш браузера чистились не раз, а реклама оставалась. Как же они продолжали отслеживать меня? Оказывается, способов для этого предостаточно.
Идентификация, отслеживание пользователя или попросту веб-трекинг подразумевает под собой расчет и установку уникального идентификатора для каждого браузера, посещающего определенный сайт. Вообще, изначально это не задумывалось каким-то вселенским злом и, как и все, имеет обратную сторону, то есть призвано приносить пользу. Например, позволить владельцам сайта отличить обычных пользователей от ботов или же предоставить возможность хранить предпочтения пользователей и применять их при последующем визите. Но в то же самое время данная возможность очень пришлась по душе рекламной индустрии. Как ты прекрасно знаешь, куки — один из самых популярных способов идентификации пользователей. И активно применяться в рекламной индустрии они начали аж с середины девяностых годов.
С тех пор многое изменилось, технологии ушли далеко вперед, и в настоящее время отслеживание пользователей одними только печеньками не ограничивается. На самом деле идентифицировать юзеров можно разными способами. Самый очевидный вариант — установить какие-либо идентификаторы, наподобие куков. Следующий вариант — воспользоваться данными об используемым юзером ПК, которые можно почерпнуть из HTTP-заголовков отправляемых запросов: адрес, тип используемой ОС, время и тому подобное. Ну и напоследок можно отличить пользователя по его поведению и привычкам (движения курсора, любимые разделы сайта и прочее).
Данный подход довольно очевиден, все, что требуется, — сохранить на стороне пользователя какой-то долгоживущий идентификатор, который можно запрашивать при последующем посещении ресурса. Современные браузеры предоставляют достаточно способов выполнить это прозрачно для пользователя. Прежде всего это старые добрые куки. Затем особенности некоторых плагинов, близкие по функционалу к кукам, например Local Shared Objects во флеше или Isolated Storage в силверлайте. HTML5 также включает в себя несколько механизмов хранения на стороне клиента, в том числе localStorage, File и IndexedDB API. Кроме этих мест, уникальные маркеры можно также хранить в кешированных ресурсах локальной машины или метаданных кеша (Last-Modified, ETag). Помимо этого, можно идентифицировать пользователя по отпечаткам, полученным из Origin Bound сертификатов, сгенерированных браузером для SSL-соединений, по данным, содержащимся в SDCH-словарях, и метаданным этих словарей. Одним словом — возможностей полно.
Когда дело касается хранения какого-то небольшого объема данных на стороне клиента, куки — это первое, что обычно приходит на ум. Веб-сервер устанавливает уникальный идентификатор для нового пользователя, сохраняя его в куках, и при всех последующих запросах клиент будет отправлять его серверу. И хотя все популярные браузеры уже давно снабжены удобным интерфейсом по управлению куками, а в Сети полно сторонних утилит для управления ими и их блокировки, куки все равно продолжают активно использоваться для трекинга пользователей. Дело в том, что мало кто просматривает и чистит их (вспомни, когда ты занимался этим последний раз). Пожалуй, основная причина этого — все боятся случайно удалить нужную «печеньку», которая, например, может использоваться для авторизации. И хотя некоторые браузеры позволяют ограничивать установку сторонних куков, проблема не исчезает, так как очень часто браузеры считают «родными» куки, полученные через HTTP-редиректы или другие способы во время загрузки контента страницы. В отличие от большинства механизмов, о которых мы поговорим далее, использование куков прозрачно для конечного пользователя. Для того чтобы «пометить» юзера, необязательно даже хранить уникальный идентификатор в отдельной куке — он может собираться из значений нескольких куков или храниться в метаданных, таких как Expiration Time. Поэтому на данном этапе довольно непросто разобраться, используется ли конкретная кука для трекинга или нет.
Для хранения данных на стороне клиента в Adobe Flash используется механизм LSO. Он является аналогом cookies в HTTP, но в отличие от последних может хранить не только короткие фрагменты текстовых данных, что, в свою очередь, усложняет анализ и проверку таких объектов. До версии 10.3 поведение флеш-куков настраивалось отдельно от настроек браузера: нужно было посетить менеджер настроек Flash, расположенный на сайте macromedia.com. Сегодня это можно выполнить непосредственно из контрольной панели. К тому же большинство современных браузеров обеспечивают достаточно плотную интеграцию с флеш-плеером: так, при удалении куков и других данных сайтов будут также удалены и LSO. С другой стороны, взаимодействие браузеров с плеером еще не настолько тесное, поэтому настройка в браузере политики для сторонних куков не всегда затронет флеш-куки (на сайте Adobe можно посмотреть, как вручную их отключить).
Программная платформа Silverlight имеет довольно много общего с Adobe Flash. Так, аналогом флешевого Local Shared Objects служит механизм под названием Isolated Storage. Правда, в отличие от флеша настройки приватности тут никак не завязаны с браузером, поэтому даже в случае полной очистки куков и кеша браузера данные, сохраненные в Isolated Storage, все равно останутся. Но еще интересней, что хранилище оказывается общим для всех окон браузера (кроме открытых в режиме «Инкогнито») и всех профилей, установленных на одной машине. Как и в LSO, с технической точки зрения здесь нет каких-либо препятствий для хранения идентификаторов сессии. Тем не менее, учитывая, что достучаться до этого механизма через настройки браузера пока нельзя, он не получил такого широкого распространения в качестве хранилища для уникальных идентификаторов.
HTML5 представляет набор механизмов для хранения структурированных данных на клиенте. К ним относятся localStorage, File API и IndexedDB. Несмотря на различия, все они предназначены для обеспечения постоянного хранения произвольных порций бинарных данных, привязанных к конкретному ресурсу. Плюс, в отличие от HTTP- и Flash-куков, здесь нет каких-либо значительных ограничений на размер хранимых данных. В современных браузерах HTML5-хранилище располагается наряду с другими данными сайта. Однако как управлять хранилищем через настройки браузера — догадаться очень трудно. К примеру, чтобы удалить данные из localStorage в Firefox, пользователю придется выбрать offline website data или site preferences и задать временной промежуток равным everything. Еще одна неординарная фишка, присущая только IE, — данные существуют только на время жизни табов, открытых в момент их сохранения. Плюс ко всему вышеперечисленные механизмы не особо-то стараются следовать ограничениям, применимым к HTTP-кукам. Например, можно писать в localStorage и читать из него через кросс-доменные фреймы даже при отключенных сторонних куках.
Все хотят, чтобы браузер работал шустро и без тормозов. Поэтому ему приходится складывать в локальный кеш ресурсы посещаемых сайтов (чтобы не запрашивать их при последующем визите). И хотя данный механизм явно не предназначался для использования в качестве хранилища с произвольным доступом, его можно в таковой превратить. Например, сервер может вернуть пользователю JavaScript-документ с уникальным идентификатором внутри его тела и установить в заголовках Expires / max-age= далекое будущее. Таким образом скрипт, а с ним и уникальный идентификатор пропишется в кеше браузера. После чего к нему можно будет обратиться с любой страницы в Сети, просто запросив загрузку скрипта с известного URL’а. Конечно, браузер будет периодически спрашивать с помощью заголовка If-Modified-Since, не появилась ли новая версия скрипта. Но если сервер будет возвращать код 304 (Not modified), то закешированная копия будет использоваться вечно. Чем еще интересен кеш? Здесь нет концепции «сторонних» объектов, как, например, в случае с HTTP-куками. В то же время отключение кеширования может серьезно отразиться на производительности. А автоматическое определение хитрых ресурсов, хранящих в себе какие-то идентификаторы/метки, затруднено в связи с большим объемом и сложностью JavaScript-документов, встречающихся в Сети. Конечно, все браузеры позволяют юзеру вручную чистить кеш. Но как показывает практика (даже собственный пример), производится это не так часто, если производится вообще.
Для того чтобы кеширование работало правильно, серверу необходимо каким-то образом информировать браузер о том, что доступна более новая версия документа. Стандарт HTTP/1.1 предлагает два способа для решения этой задачи. Первый основан на дате последнего изменения документа, а второй — на абстрактном идентификаторе, известном как ETag. В случае с ETag сервер изначально возвращает так называемый version tag в заголовке ответа вместе с самим документом. При последующих запросах к заданному URL клиент сообщает серверу через заголовок If-None-Match это значение, ассоциированное с его локальной копией. Если версия, указанная в этом заголовке, актуальная, то сервер отвечает HTTP-кодом 304 (Not Modified), и клиент может спокойно использовать кешированную версию. В противном случае сервер присылает новую версию документа с новым ETag. Такой подход чем-то напоминает HTTP-куки — сервер сохраняет произвольное значение на клиенте только для того, чтобы потом его считать. Другой способ, связанный с использованием заголовка Last-Modified, позволяет хранить по крайней мере 32 бита данных в строке даты, которая затем отправляется клиентом серверу в заголовке If-Modified-Since. Что интересно, большинство браузеров даже не требуют, чтобы эта строка представляла собой дату в правильном формате. Как и в случае идентификации пользователя через кешированные объекты, на ETag и Last-Modified никак не влияет удаление куков и данных сайта, избавиться от них можно только очисткой кеша.
Application Cache позволяет задавать, какая часть сайта должна быть сохранена на диске и быть доступна, даже если пользователь находится офлайн. Управляется все с помощью манифестов, которые задают правила для хранения и извлечения элементов кеша. Подобно традиционному механизму кеширования, AppCache тоже позволяет хранить уникальные, зависящие от пользователя данные — как внутри самого манифеста, так и внутри ресурсов, которые сохраняются на неопределенный срок (в отличие от обычного кеша, ресурсы из которого удаляются по истечении какого-то времени). AppCache занимает промежуточное значение между механизмами хранения данных в HTML5 и обычным кешем браузера. В некоторых браузерах он очищается при удалении куков и данных сайта, в других только при удалении истории просмотра и всех кешированных документов.
SDCH — это разработанный Google алгоритм компрессии, который основывается на использовании предоставляемых сервером словарей и позволяет достичь более высокого уровня сжатия, чем Gzip или deflate. Дело в том, что в обычной жизни веб-сервер отдает слишком много повторяющейся информации — хидеры/футеры страниц, встроенный JavaScript/CSS и так далее. В данном подходе клиент получает с сервера файл словаря, содержащий строки, которые могут появиться в последующих ответах (те же хидеры/футеры/JS/CSS). После чего сервер может просто ссылаться на эти элементы внутри словаря, а клиент будет самостоятельно на их основе собирать страницу. Как ты понимаешь, эти словари можно с легкостью использовать и для хранения уникальных идентификаторов, которые можно поместить как в ID словарей, возвращаемые клиентом серверу в заголовке Avail-Dictionary, так и непосредственно в сам контент. И потом использовать подобно как и в случае с обычным кешем браузера.
Но это еще не все варианты. При помощи JavaScript и его товарищей по цеху можно сохранять и запрашивать уникальный идентификатор таким образом, что он останется в живых даже после удаления всей истории просмотров и данных сайтов. Как один из вариантов, можно использовать для хранения window.name или sessionStorage. Даже если пользователь подчистит все куки и данные сайта, но не закроет вкладку, в которой был открыт отслеживающий сайт, то при последующем заходе идентифицирующий токен будет получен сервером и пользователь будет снова привязан к уже собранным о нем данным. Такое же поведение наблюдается и у JS, любой открытый JavaScript-контекст сохраняет состояние, даже если пользователь удалит данные сайта. При этом такой JavaScript может не только принадлежать отображаемому сайту, но и прятаться в iframe’ах, веб-воркерах и так далее. Например, загруженная в iframe реклама вовсе не обратит внимания на удаление истории просмотров и данных сайта и продолжит использовать идентификатор, сохраненный в локальной переменной в JS.
Помимо механизмов, связанных с кешированием, использованием JS и разных плагинов, в современных браузерах есть еще несколько сетевых фич, позволяющих хранить и извлекать уникальные идентификаторы.
Origin Bound Certificates (aka ChannelID) — персистентные самоподписанные сертификаты, идентифицирующие клиента HTTPS-серверу. Для каждого нового домена создается отдельный сертификат, который используется для соединений, инициируемых в дальнейшем. Сайты могут использовать OBC для трекинга пользователей, не предпринимая при этом каких-либо действий, которые будут заметны клиенту. В качестве уникального идентификатора можно взять криптографический хеш сертификата, предоставляемый клиентом как часть легитимного SSL-рукопожатия.
Подобным образом и в TLS тоже есть два механизма — session identifiers и session tickets, которые позволяют клиентам возобновлять прерванные HTTPS-соединения без выполнения полного рукопожатия. Достигается это за счет использования закешированных данных. Два этих механизма в течение небольшого промежутка времени позволяют серверам идентифицировать запросы, исходящие от одного клиента.
Практически все современные браузеры реализуют свой собственный внутренний DNS-кеш, чтобы ускорить процесс разрешения имен (и в некоторых случаях снизить риск DNS rebinding атак). Такой кеш запросто можно использовать для хранения небольших объемов информации. Например, если обладать 16 доступными IP-адресами, около 8–9 закешированных имен будет достаточно, чтобы идентифицировать каждый компьютер в Сети. Однако такой подход ограничен размером внутреннего DNS-кеша браузеров и может потенциально привести к конфликтам в разрешении имен с DNS провайдера.
Все рассмотренные до этого способы основывались на том, что пользователю устанавливался какой-то уникальный идентификатор, который отправлялся серверу при последующих запросах. Есть другой, менее очевидный подход к отслеживанию пользователей, полагающийся на запрос или измерение характеристик клиентской машины. Поодиночке каждая полученная характеристика представляет собой лишь несколько бит информации, но если объединить несколько, то они смогут уникально идентифицировать любой компьютер в интернете. Помимо того что такую слежку гораздо сложнее распознать и предотвратить, эта техника позволит идентифицировать пользователя, сидящего под разными браузерами или использующего приватный режим.
Наиболее простой подход к трекингу — это построение идентификаторов путем объединения набора параметров, доступных в среде браузера, каждый из которых по отдельности не представляет никакого интереса, но совместно они образуют уникальное для каждой машины значение:
User-Agent. Выдает версию браузера, версию ОС и некоторые из установленных аддонов. В случаях, когда User-Agent отсутствует или хочется проверить его «правдивость», можно определить версию браузера проверкой на наличие определенных фич, реализованных или измененных между релизами.
Ход часов. Если система не синхронизирует свои часы со сторонним сервером времени, то рано или поздно они начнут отставать или спешить, что породит уникальную разницу между реальным и системным временем, которую можно измерить с точностью до микросекунды с помощью JavaScript’а. На самом деле даже при синхронизации с NTP-сервером все равно будут небольшие отклонения, которые также можно будет измерить.
Информация о CPU и GPU. Можно получить как напрямую (через GL_RENDERER), так и через бенчмарки и тесты, реализованные с помощью JavaScript.
Разрешение монитора и размер окна браузера (включая параметры второго монитора в случае мультимониторной системы).
Список установленных в системе шрифтов, полученных, например, с помощью getComputedStyleAPI.
Список всех установленных плагинов, ActiveX-контролов, Browser Helper Object’ов, включая их версии. Можно получить перебором navigator.plugins[] (некоторые плагины выдают свое присутствие в HTTP-заголовках).
Информация об установленных расширениях и другом ПО. Такие расширения, как блокировщики рекламы, вносят определенные изменения в просматриваемые страницы, по которым можно определить, что это за расширение, и его настройки.
Еще ряд признаков кроется в архитектуре локальной сети и настройке сетевых протоколов. Такие знаки будут характерны для всех браузеров, установленных на клиентской машине, и их нельзя просто скрыть с помощью настроек приватности или каких-то security-утилит. Они включают в себя:
Внешний IP-адрес. Для IPv6-адресов данный вектор особенно интересен, так как последние октеты в некоторых случаях могут получаться из MAC-адреса устройства и потому сохраняться даже при подключении к разным сетям.
Номера портов для исходящих TCP/IP-соединений (обычно выбираются последовательно для большинства ОС).
Локальный IP-адрес для пользователей, находящихся за NAT’ом или HTTP-прокси. Вкупе с внешним айпишником позволяет уникально идентифицировать большинство клиентов.
Информация об используемых клиентом прокси-серверах, полученная из HTTP-заголовка (X-Forwarded-For). В сочетании с реальным адресом клиента, полученным через несколько возможных способов обхода прокси, также позволяет идентифицировать пользователя.
Еще один вариант — взглянуть в сторону характеристик, которые привязаны не к ПК, а скорее к конечному пользователю, такие как региональные настройки и поведение. Такой способ опять же позволит идентифицировать клиентов между различными сессиями браузера, профилями и в случае приватного просмотра. Делать выводы можно на основании следующих данных, которые всегда доступны для изучения:
Предпочитаемый язык, дефолтная кодировка и часовой пояс (все это живет в HTTP-заголовках и доступно из JavaScript).
Данные в кеше клиента и его история просмотра. Элементы кеша можно обнаружить при помощи атак по времени — отслеживающий может обнаружить долгоживущие элементы кеша, относящиеся к популярным ресурсам, просто измерив время из загрузки (и отменив переход, если время превышает ожидаемое время загрузки из локального кеша). Также можно извлекать URL’ы, хранящиеся в истории просмотра браузера, хотя такая атака в современных браузерах потребует небольшого взаимодействия с пользователем.
Жесты мышью, частота и продолжительность нажатия клавиш, данные с акселерометра — все эти параметры уникальны для каждого пользователя.
Любые изменения стандартных шрифтов сайта и их размеров, уровень zoom’а, использование специальных возможностей, таких как цвет текста, размер.
Состояние определенных фич браузера, настраиваемых клиентом: блокировка сторонних куков, DNS prefetching, блокировка всплывающих окон, настройки безопасности Flash и так далее (по иронии, пользователи, меняющие дефолтные настройки, в действительности делают свой браузер значительно более легким для идентификации).
И это лишь очевидные варианты, которые лежат на поверхности. Если копнуть глубже — можно придумать еще.
Как видишь, на практике существует большое число различных способов для трекинга пользователя. Какие-то из них являются плодом ошибок в реализации или упущений и теоретически могут быть исправлены. Другие практически невозможно искоренить без полного изменения принципов работы компьютерных сетей, веб-приложений, браузеров. Каким-то техникам можно противодействовать — чистить кеш, куки и прочие места, где могут храниться уникальные идентификаторы. Другие работают абсолютно незаметно для пользователя, и защититься от них вряд ли получится. Поэтому самое главное — путешествуя по Сети даже в приватном режиме просмотра, помнить, что твои перемещения все равно могут отследить.
Небольшое предисловие
Идентификация, отслеживание пользователя или попросту веб-трекинг подразумевает под собой расчет и установку уникального идентификатора для каждого браузера, посещающего определенный сайт. Вообще, изначально это не задумывалось каким-то вселенским злом и, как и все, имеет обратную сторону, то есть призвано приносить пользу. Например, позволить владельцам сайта отличить обычных пользователей от ботов или же предоставить возможность хранить предпочтения пользователей и применять их при последующем визите. Но в то же самое время данная возможность очень пришлась по душе рекламной индустрии. Как ты прекрасно знаешь, куки — один из самых популярных способов идентификации пользователей. И активно применяться в рекламной индустрии они начали аж с середины девяностых годов.
С тех пор многое изменилось, технологии ушли далеко вперед, и в настоящее время отслеживание пользователей одними только печеньками не ограничивается. На самом деле идентифицировать юзеров можно разными способами. Самый очевидный вариант — установить какие-либо идентификаторы, наподобие куков. Следующий вариант — воспользоваться данными об используемым юзером ПК, которые можно почерпнуть из HTTP-заголовков отправляемых запросов: адрес, тип используемой ОС, время и тому подобное. Ну и напоследок можно отличить пользователя по его поведению и привычкам (движения курсора, любимые разделы сайта и прочее).
Явные идентификаторы
Данный подход довольно очевиден, все, что требуется, — сохранить на стороне пользователя какой-то долгоживущий идентификатор, который можно запрашивать при последующем посещении ресурса. Современные браузеры предоставляют достаточно способов выполнить это прозрачно для пользователя. Прежде всего это старые добрые куки. Затем особенности некоторых плагинов, близкие по функционалу к кукам, например Local Shared Objects во флеше или Isolated Storage в силверлайте. HTML5 также включает в себя несколько механизмов хранения на стороне клиента, в том числе localStorage, File и IndexedDB API. Кроме этих мест, уникальные маркеры можно также хранить в кешированных ресурсах локальной машины или метаданных кеша (Last-Modified, ETag). Помимо этого, можно идентифицировать пользователя по отпечаткам, полученным из Origin Bound сертификатов, сгенерированных браузером для SSL-соединений, по данным, содержащимся в SDCH-словарях, и метаданным этих словарей. Одним словом — возможностей полно.
Cookies
Когда дело касается хранения какого-то небольшого объема данных на стороне клиента, куки — это первое, что обычно приходит на ум. Веб-сервер устанавливает уникальный идентификатор для нового пользователя, сохраняя его в куках, и при всех последующих запросах клиент будет отправлять его серверу. И хотя все популярные браузеры уже давно снабжены удобным интерфейсом по управлению куками, а в Сети полно сторонних утилит для управления ими и их блокировки, куки все равно продолжают активно использоваться для трекинга пользователей. Дело в том, что мало кто просматривает и чистит их (вспомни, когда ты занимался этим последний раз). Пожалуй, основная причина этого — все боятся случайно удалить нужную «печеньку», которая, например, может использоваться для авторизации. И хотя некоторые браузеры позволяют ограничивать установку сторонних куков, проблема не исчезает, так как очень часто браузеры считают «родными» куки, полученные через HTTP-редиректы или другие способы во время загрузки контента страницы. В отличие от большинства механизмов, о которых мы поговорим далее, использование куков прозрачно для конечного пользователя. Для того чтобы «пометить» юзера, необязательно даже хранить уникальный идентификатор в отдельной куке — он может собираться из значений нескольких куков или храниться в метаданных, таких как Expiration Time. Поэтому на данном этапе довольно непросто разобраться, используется ли конкретная кука для трекинга или нет.
Local Shared Objects
Для хранения данных на стороне клиента в Adobe Flash используется механизм LSO. Он является аналогом cookies в HTTP, но в отличие от последних может хранить не только короткие фрагменты текстовых данных, что, в свою очередь, усложняет анализ и проверку таких объектов. До версии 10.3 поведение флеш-куков настраивалось отдельно от настроек браузера: нужно было посетить менеджер настроек Flash, расположенный на сайте macromedia.com. Сегодня это можно выполнить непосредственно из контрольной панели. К тому же большинство современных браузеров обеспечивают достаточно плотную интеграцию с флеш-плеером: так, при удалении куков и других данных сайтов будут также удалены и LSO. С другой стороны, взаимодействие браузеров с плеером еще не настолько тесное, поэтому настройка в браузере политики для сторонних куков не всегда затронет флеш-куки (на сайте Adobe можно посмотреть, как вручную их отключить).
Изолированное хранилище Silverlight
Программная платформа Silverlight имеет довольно много общего с Adobe Flash. Так, аналогом флешевого Local Shared Objects служит механизм под названием Isolated Storage. Правда, в отличие от флеша настройки приватности тут никак не завязаны с браузером, поэтому даже в случае полной очистки куков и кеша браузера данные, сохраненные в Isolated Storage, все равно останутся. Но еще интересней, что хранилище оказывается общим для всех окон браузера (кроме открытых в режиме «Инкогнито») и всех профилей, установленных на одной машине. Как и в LSO, с технической точки зрения здесь нет каких-либо препятствий для хранения идентификаторов сессии. Тем не менее, учитывая, что достучаться до этого механизма через настройки браузера пока нельзя, он не получил такого широкого распространения в качестве хранилища для уникальных идентификаторов.
HTML5 и хранение данных на клиенте
HTML5 представляет набор механизмов для хранения структурированных данных на клиенте. К ним относятся localStorage, File API и IndexedDB. Несмотря на различия, все они предназначены для обеспечения постоянного хранения произвольных порций бинарных данных, привязанных к конкретному ресурсу. Плюс, в отличие от HTTP- и Flash-куков, здесь нет каких-либо значительных ограничений на размер хранимых данных. В современных браузерах HTML5-хранилище располагается наряду с другими данными сайта. Однако как управлять хранилищем через настройки браузера — догадаться очень трудно. К примеру, чтобы удалить данные из localStorage в Firefox, пользователю придется выбрать offline website data или site preferences и задать временной промежуток равным everything. Еще одна неординарная фишка, присущая только IE, — данные существуют только на время жизни табов, открытых в момент их сохранения. Плюс ко всему вышеперечисленные механизмы не особо-то стараются следовать ограничениям, применимым к HTTP-кукам. Например, можно писать в localStorage и читать из него через кросс-доменные фреймы даже при отключенных сторонних куках.
Кешированные объекты
Все хотят, чтобы браузер работал шустро и без тормозов. Поэтому ему приходится складывать в локальный кеш ресурсы посещаемых сайтов (чтобы не запрашивать их при последующем визите). И хотя данный механизм явно не предназначался для использования в качестве хранилища с произвольным доступом, его можно в таковой превратить. Например, сервер может вернуть пользователю JavaScript-документ с уникальным идентификатором внутри его тела и установить в заголовках Expires / max-age= далекое будущее. Таким образом скрипт, а с ним и уникальный идентификатор пропишется в кеше браузера. После чего к нему можно будет обратиться с любой страницы в Сети, просто запросив загрузку скрипта с известного URL’а. Конечно, браузер будет периодически спрашивать с помощью заголовка If-Modified-Since, не появилась ли новая версия скрипта. Но если сервер будет возвращать код 304 (Not modified), то закешированная копия будет использоваться вечно. Чем еще интересен кеш? Здесь нет концепции «сторонних» объектов, как, например, в случае с HTTP-куками. В то же время отключение кеширования может серьезно отразиться на производительности. А автоматическое определение хитрых ресурсов, хранящих в себе какие-то идентификаторы/метки, затруднено в связи с большим объемом и сложностью JavaScript-документов, встречающихся в Сети. Конечно, все браузеры позволяют юзеру вручную чистить кеш. Но как показывает практика (даже собственный пример), производится это не так часто, если производится вообще.
ETag и Last-Modified
Для того чтобы кеширование работало правильно, серверу необходимо каким-то образом информировать браузер о том, что доступна более новая версия документа. Стандарт HTTP/1.1 предлагает два способа для решения этой задачи. Первый основан на дате последнего изменения документа, а второй — на абстрактном идентификаторе, известном как ETag. В случае с ETag сервер изначально возвращает так называемый version tag в заголовке ответа вместе с самим документом. При последующих запросах к заданному URL клиент сообщает серверу через заголовок If-None-Match это значение, ассоциированное с его локальной копией. Если версия, указанная в этом заголовке, актуальная, то сервер отвечает HTTP-кодом 304 (Not Modified), и клиент может спокойно использовать кешированную версию. В противном случае сервер присылает новую версию документа с новым ETag. Такой подход чем-то напоминает HTTP-куки — сервер сохраняет произвольное значение на клиенте только для того, чтобы потом его считать. Другой способ, связанный с использованием заголовка Last-Modified, позволяет хранить по крайней мере 32 бита данных в строке даты, которая затем отправляется клиентом серверу в заголовке If-Modified-Since. Что интересно, большинство браузеров даже не требуют, чтобы эта строка представляла собой дату в правильном формате. Как и в случае идентификации пользователя через кешированные объекты, на ETag и Last-Modified никак не влияет удаление куков и данных сайта, избавиться от них можно только очисткой кеша.
HTML5 AppCache
Application Cache позволяет задавать, какая часть сайта должна быть сохранена на диске и быть доступна, даже если пользователь находится офлайн. Управляется все с помощью манифестов, которые задают правила для хранения и извлечения элементов кеша. Подобно традиционному механизму кеширования, AppCache тоже позволяет хранить уникальные, зависящие от пользователя данные — как внутри самого манифеста, так и внутри ресурсов, которые сохраняются на неопределенный срок (в отличие от обычного кеша, ресурсы из которого удаляются по истечении какого-то времени). AppCache занимает промежуточное значение между механизмами хранения данных в HTML5 и обычным кешем браузера. В некоторых браузерах он очищается при удалении куков и данных сайта, в других только при удалении истории просмотра и всех кешированных документов.
SDCH-словари
SDCH — это разработанный Google алгоритм компрессии, который основывается на использовании предоставляемых сервером словарей и позволяет достичь более высокого уровня сжатия, чем Gzip или deflate. Дело в том, что в обычной жизни веб-сервер отдает слишком много повторяющейся информации — хидеры/футеры страниц, встроенный JavaScript/CSS и так далее. В данном подходе клиент получает с сервера файл словаря, содержащий строки, которые могут появиться в последующих ответах (те же хидеры/футеры/JS/CSS). После чего сервер может просто ссылаться на эти элементы внутри словаря, а клиент будет самостоятельно на их основе собирать страницу. Как ты понимаешь, эти словари можно с легкостью использовать и для хранения уникальных идентификаторов, которые можно поместить как в ID словарей, возвращаемые клиентом серверу в заголовке Avail-Dictionary, так и непосредственно в сам контент. И потом использовать подобно как и в случае с обычным кешем браузера.
Прочие механизмы хранения
Но это еще не все варианты. При помощи JavaScript и его товарищей по цеху можно сохранять и запрашивать уникальный идентификатор таким образом, что он останется в живых даже после удаления всей истории просмотров и данных сайтов. Как один из вариантов, можно использовать для хранения window.name или sessionStorage. Даже если пользователь подчистит все куки и данные сайта, но не закроет вкладку, в которой был открыт отслеживающий сайт, то при последующем заходе идентифицирующий токен будет получен сервером и пользователь будет снова привязан к уже собранным о нем данным. Такое же поведение наблюдается и у JS, любой открытый JavaScript-контекст сохраняет состояние, даже если пользователь удалит данные сайта. При этом такой JavaScript может не только принадлежать отображаемому сайту, но и прятаться в iframe’ах, веб-воркерах и так далее. Например, загруженная в iframe реклама вовсе не обратит внимания на удаление истории просмотров и данных сайта и продолжит использовать идентификатор, сохраненный в локальной переменной в JS.
Протоколы
Помимо механизмов, связанных с кешированием, использованием JS и разных плагинов, в современных браузерах есть еще несколько сетевых фич, позволяющих хранить и извлекать уникальные идентификаторы.
Origin Bound Certificates (aka ChannelID) — персистентные самоподписанные сертификаты, идентифицирующие клиента HTTPS-серверу. Для каждого нового домена создается отдельный сертификат, который используется для соединений, инициируемых в дальнейшем. Сайты могут использовать OBC для трекинга пользователей, не предпринимая при этом каких-либо действий, которые будут заметны клиенту. В качестве уникального идентификатора можно взять криптографический хеш сертификата, предоставляемый клиентом как часть легитимного SSL-рукопожатия.
Подобным образом и в TLS тоже есть два механизма — session identifiers и session tickets, которые позволяют клиентам возобновлять прерванные HTTPS-соединения без выполнения полного рукопожатия. Достигается это за счет использования закешированных данных. Два этих механизма в течение небольшого промежутка времени позволяют серверам идентифицировать запросы, исходящие от одного клиента.
Практически все современные браузеры реализуют свой собственный внутренний DNS-кеш, чтобы ускорить процесс разрешения имен (и в некоторых случаях снизить риск DNS rebinding атак). Такой кеш запросто можно использовать для хранения небольших объемов информации. Например, если обладать 16 доступными IP-адресами, около 8–9 закешированных имен будет достаточно, чтобы идентифицировать каждый компьютер в Сети. Однако такой подход ограничен размером внутреннего DNS-кеша браузеров и может потенциально привести к конфликтам в разрешении имен с DNS провайдера.
Характеристики машины
Все рассмотренные до этого способы основывались на том, что пользователю устанавливался какой-то уникальный идентификатор, который отправлялся серверу при последующих запросах. Есть другой, менее очевидный подход к отслеживанию пользователей, полагающийся на запрос или измерение характеристик клиентской машины. Поодиночке каждая полученная характеристика представляет собой лишь несколько бит информации, но если объединить несколько, то они смогут уникально идентифицировать любой компьютер в интернете. Помимо того что такую слежку гораздо сложнее распознать и предотвратить, эта техника позволит идентифицировать пользователя, сидящего под разными браузерами или использующего приватный режим.
«Отпечатки» браузера
Наиболее простой подход к трекингу — это построение идентификаторов путем объединения набора параметров, доступных в среде браузера, каждый из которых по отдельности не представляет никакого интереса, но совместно они образуют уникальное для каждой машины значение:
User-Agent. Выдает версию браузера, версию ОС и некоторые из установленных аддонов. В случаях, когда User-Agent отсутствует или хочется проверить его «правдивость», можно определить версию браузера проверкой на наличие определенных фич, реализованных или измененных между релизами.
Ход часов. Если система не синхронизирует свои часы со сторонним сервером времени, то рано или поздно они начнут отставать или спешить, что породит уникальную разницу между реальным и системным временем, которую можно измерить с точностью до микросекунды с помощью JavaScript’а. На самом деле даже при синхронизации с NTP-сервером все равно будут небольшие отклонения, которые также можно будет измерить.
Информация о CPU и GPU. Можно получить как напрямую (через GL_RENDERER), так и через бенчмарки и тесты, реализованные с помощью JavaScript.
Разрешение монитора и размер окна браузера (включая параметры второго монитора в случае мультимониторной системы).
Список установленных в системе шрифтов, полученных, например, с помощью getComputedStyleAPI.
Список всех установленных плагинов, ActiveX-контролов, Browser Helper Object’ов, включая их версии. Можно получить перебором navigator.plugins[] (некоторые плагины выдают свое присутствие в HTTP-заголовках).
Информация об установленных расширениях и другом ПО. Такие расширения, как блокировщики рекламы, вносят определенные изменения в просматриваемые страницы, по которым можно определить, что это за расширение, и его настройки.
Сетевые «отпечатки»
Еще ряд признаков кроется в архитектуре локальной сети и настройке сетевых протоколов. Такие знаки будут характерны для всех браузеров, установленных на клиентской машине, и их нельзя просто скрыть с помощью настроек приватности или каких-то security-утилит. Они включают в себя:
Внешний IP-адрес. Для IPv6-адресов данный вектор особенно интересен, так как последние октеты в некоторых случаях могут получаться из MAC-адреса устройства и потому сохраняться даже при подключении к разным сетям.
Номера портов для исходящих TCP/IP-соединений (обычно выбираются последовательно для большинства ОС).
Локальный IP-адрес для пользователей, находящихся за NAT’ом или HTTP-прокси. Вкупе с внешним айпишником позволяет уникально идентифицировать большинство клиентов.
Информация об используемых клиентом прокси-серверах, полученная из HTTP-заголовка (X-Forwarded-For). В сочетании с реальным адресом клиента, полученным через несколько возможных способов обхода прокси, также позволяет идентифицировать пользователя.
Поведенческий анализ и привычки
Еще один вариант — взглянуть в сторону характеристик, которые привязаны не к ПК, а скорее к конечному пользователю, такие как региональные настройки и поведение. Такой способ опять же позволит идентифицировать клиентов между различными сессиями браузера, профилями и в случае приватного просмотра. Делать выводы можно на основании следующих данных, которые всегда доступны для изучения:
Предпочитаемый язык, дефолтная кодировка и часовой пояс (все это живет в HTTP-заголовках и доступно из JavaScript).
Данные в кеше клиента и его история просмотра. Элементы кеша можно обнаружить при помощи атак по времени — отслеживающий может обнаружить долгоживущие элементы кеша, относящиеся к популярным ресурсам, просто измерив время из загрузки (и отменив переход, если время превышает ожидаемое время загрузки из локального кеша). Также можно извлекать URL’ы, хранящиеся в истории просмотра браузера, хотя такая атака в современных браузерах потребует небольшого взаимодействия с пользователем.
Жесты мышью, частота и продолжительность нажатия клавиш, данные с акселерометра — все эти параметры уникальны для каждого пользователя.
Любые изменения стандартных шрифтов сайта и их размеров, уровень zoom’а, использование специальных возможностей, таких как цвет текста, размер.
Состояние определенных фич браузера, настраиваемых клиентом: блокировка сторонних куков, DNS prefetching, блокировка всплывающих окон, настройки безопасности Flash и так далее (по иронии, пользователи, меняющие дефолтные настройки, в действительности делают свой браузер значительно более легким для идентификации).
И это лишь очевидные варианты, которые лежат на поверхности. Если копнуть глубже — можно придумать еще.
Подытожим
Как видишь, на практике существует большое число различных способов для трекинга пользователя. Какие-то из них являются плодом ошибок в реализации или упущений и теоретически могут быть исправлены. Другие практически невозможно искоренить без полного изменения принципов работы компьютерных сетей, веб-приложений, браузеров. Каким-то техникам можно противодействовать — чистить кеш, куки и прочие места, где могут храниться уникальные идентификаторы. Другие работают абсолютно незаметно для пользователя, и защититься от них вряд ли получится. Поэтому самое главное — путешествуя по Сети даже в приватном режиме просмотра, помнить, что твои перемещения все равно могут отследить.