AntiDetect
Возможности Технология Цены Для кого
Документация Блог FAQ
Язык
EnglishItalianoDeutschEspañolРусский
Скачать

Список ваших шрифтов — это признание операционной системы

Ни один API не сообщает сайту, какие шрифты у вас установлены. Вместо этого сайт их измеряет — рисует текст и сравнивает ширину, несколько сотен раз, меньше чем за пятьдесят миллисекунд. Результат несёт около четырнадцати бит и без колебаний опровергнет подменённый user agent.

Измерение, а не перечисление

Веб-API, возвращающего список установленных шрифтов, не существовало никогда. И не требовалось: измерение работает почти так же хорошо и не спрашивает никаких разрешений.

Приём тот же, что используется примерно с 2007 года. Отрисуйте строку известным запасным шрифтом и запомните ширину. Затем отрисуйте ту же строку, запросив сначала проверяемый шрифт, а тот же запасной оставив позади. Если ширина изменилась — шрифт есть и был использован. Если нет — шрифта нет, и снова рисовал запасной.

const span = document.createElement('span');
span.style.cssText = 'position:absolute;left:-9999px;font-size:72px';
span.textContent = 'mmmmmmmmmmlli';

span.style.fontFamily = 'monospace';
document.body.appendChild(span);
const base = span.offsetWidth;

span.style.fontFamily = '"Segoe UI", monospace';
const installed = span.offsetWidth !== base;

Тестовая строка не случайна. mmmmmmmmmmlli смешивает самые широкие и самые узкие глифы большинства латинских гарнитур, максимизируя разницу ширины между любыми двумя шрифтами. При 72 пикселях разницу невозможно не заметить.

Прогоните это по списку из двух-трёх сотен кандидатов — и у вас есть шрифтовой профиль. Стоит это несколько десятков миллисекунд и не оставляет следа ни в одном запросе разрешений.

Одна и та же тестовая строка, отрисованная двумя гарнитурами, с измеренными ширинами внизу
Весь приём целиком. Отрисуйте строку проверяемым шрифтом с известным запасным позади: если ширина сдвинулась, шрифт установлен.

Почему шрифты выдают операционную систему

Наборы шрифтов не случайны. Они приходят вместе с ОС, и у каждой платформы набор свой, узнаваемый.

ШрифтПоставляется сЧто означает его наличие
Segoe UIWindows Vista и новееWindows, почти наверняка
Segoe UI VariableWindows 11Отличает 11 от 10
Helvetica NeuemacOSПлатформа Apple
Ubuntu, Liberation SansРаспространённые дистрибутивы LinuxLinux
Calibri, CambriaMicrosoft OfficeOffice установлен; слабый признак Windows
Arial, Times New RomanПочти вездеНичего

Это та проверка, которая ловит подменённый user agent даром. Профиль, заявляющий Windows 11 и не способный отрисовать Segoe UI Variable, противоречит сам себе в одном сравнении offsetWidth — без внешних данных, без базы репутации, без машинного обучения.

Обратная ошибка так же часта и заметна хуже: профиль, заявляющий macOS на хосте с Windows, где Segoe UI хоста никуда не делся и прекрасно измеряется. User agent говорит Mac; шрифты говорят Windows; побеждают шрифты.

Сколько это стоит

Работа EFF Panopticlick оценила списки шрифтов примерно в 13,9 бита опознавательной информации — этого одного хватает, чтобы сузить браузер до одного примерно из пятнадцати тысяч. Это один из самых высоких показателей для отдельного сигнала во всей литературе о фингерпринтинге: выше разрешения экрана и намного выше строки user agent.

Две оговорки, чтобы всё было честно. Во-первых, эта цифра получена на самоотобранной выборке пользователей, интересующихся приватностью, и завышает энтропию для веба в целом. Во-вторых, она снижается: Windows 11, свежие версии macOS и массовые дистрибутивы Linux поставляют всё более похожие наборы по умолчанию, а веб-шрифты убрали почти всякий повод ставить что-то дополнительно.

Что переживает это снижение — как раз та часть, которая здесь важна. Даже если список шрифтов больше не опознаёт машину однозначно, он по-прежнему опознаёт платформу, а именно определение платформы и ломает несогласованный профиль.

Другие способы спросить

Измерение ширины — классический метод, но не единственный. Реализация, перехватывающая только offsetWidth, эти способы пропустит.

measureText у canvas

ctx.measureText() возвращает объект TextMetrics с субпиксельной шириной продвижения и, в современных браузерах, реальными верхним и нижним выносами ограничивающего прямоугольника. Это точнее, чем измерение по элементам, и живёт в canvas-API, а значит, может выполняться внутри OffscreenCanvas в worker, куда перехваты на уровне страницы не дотягиваются.

Чистый CSS

Определять шрифты можно вообще без JavaScript — через @font-face с unicode-range и запрос фонового изображения, который срабатывает, только если использован конкретный запасной шрифт. Ни скрипта для перехвата, ни API для подмены.

Local Font Access

В Chrome 103 появился window.queryLocalFonts(), возвращающий полный список локальных шрифтов напрямую — но за запросом разрешения у пользователя. На практике скрипты фингерпринтинга им не пользуются как раз потому, что он спрашивает. Знать о нём стоит главным образом потому, что у браузера, заявляющего Chrome 147, этот API должен присутствовать, а профиль, который его убрал, создал новое несоответствие.

Как сделать правильно

Работают два подхода, а один очень распространённый — нет.

Что не работает: отдавать пустой или минимальный список. Браузер с пятью шрифтами приметнее, чем браузер с двумя сотнями. Это та же ошибка, что и рандомизация canvas: вы заменили распространённое значение редким и назвали это приватностью.

Подход первый: поставлять правильный набор. Профиль заявляет Windows 11, значит, список шрифтов — набор Windows 11 по умолчанию, присутствующий и измеримый. Полностью это достижимо, только когда под контролем сам шрифтовой стек: браузер должен разрешать запросы шрифтов по объявленному набору, а не по тому, что установлено на хосте. Это задача уровня движка, и потому одна из самых трудных для доработки задним числом.

Подход второй: не заявляйте платформу, на которой вы не находитесь. Профили Windows запускайте на Windows, профили Mac — на Mac. Без блеска, полностью действенно и бесплатно. Если у вас есть выбор хоста, это самый дешёвый правильный ответ.

Каким бы путём вы ни пошли, действует ограничение из статьи о согласованности: набор шрифтов должен согласовываться с user agent, с рендерером WebGL и со строкой платформы. Шрифты просто самые дешёвые из четырёх для проверки, поэтому их и проверяют первыми.

Проверить свои

Самый быстрый тест — одна проба против заявленной вами ОС. Вставьте это в консоль профиля и прочитайте ответ:

function has(f) {
  const s = document.createElement('span');
  s.style.cssText = 'position:absolute;left:-9999px;font-size:72px';
  s.textContent = 'mmmmmmmmmmlli';
  document.body.appendChild(s);
  s.style.fontFamily = 'monospace';       const base = s.offsetWidth;
  s.style.fontFamily = `"${f}", monospace`; const w = s.offsetWidth;
  s.remove();
  return w !== base;
}
console.log({
  windows: has('Segoe UI'),
  win11:   has('Segoe UI Variable'),
  macos:   has('Helvetica Neue'),
  linux:   has('Ubuntu')
});

Истинным должно оказаться ровно одно из них — то, что соответствует вашему user agent. Два истинных значения означают, что ваш профиль носит сразу две операционные системы. Для более полной картины browserleaks.com/fonts и CreepJS перечисляют весь набор, а CreepJS вдобавок прямо скажет, когда список не согласуется с заявленной платформой.

Наборы шрифтов, соответствующие машине

P8 поставляет каждый профиль с набором шрифтов по умолчанию для той версии ОС, которую профиль заявляет, — а не шрифты хоста с другим user agent сверху.

Начать бесплатно
Все статьи Далее: почему рекламные аккаунты банят на самом деле