navigator.mediaDevices.enumerateDevices() раскрывает число, ID и метки устройств ещё до запроса доступа к камере или микрофону. Как это используют трекеры.
Каждый ноутбук со встроенной веб-камерой и микрофоном, каждый десктоп с подключённой по USB гарнитурой, каждый телефон с двумя камерами — всё это видно веб-странице через один неприметный вызов API. navigator.mediaDevices.enumerateDevices() изначально создавался для того, чтобы сайт мог перечислить доступные камеры и микрофоны перед началом видеозвонка. Он с этим справляется, но заодно отдаёт сигнал для фингерпринтинга, который не требует вообще никакого запроса разрешения, а в момент, когда пользователь всё же выдаёт доступ к камере или микрофону, становится ещё богаче.
Ключевые выводы
enumerateDevices()работает без запроса разрешения и возвращает количество и тип подключённых аудио/видеоустройств любой странице, которая его вызовет.- До выдачи разрешения поля
deviceIdиlabelпусты, но количество и тип устройств (сколько камер, микрофонов, динамиков) всё равно добавляют энтропию. - После выдачи разрешения (даже единожды, любому сайту) становятся видны
deviceId,groupIdи человекочитаемые строкиlabel— часто включающие название модели оборудования. groupIdсвязывает устройства, принадлежащие одному физическому оборудованию, позволяя скрипту сделать вывод, например, что камера и микрофон — часть одной веб-камеры.- Само количество устройств — значимый межсайтовый сигнал: большинство десктопов сообщают небольшой, стабильный набор устройств, сохраняющийся между сессиями и даже после перезапуска браузера.
Что возвращает enumerateDevices()
Метод MediaDevices.enumerateDevices(), часть спецификации Media Capture and Streams, возвращает промис, который разрешается в массив объектов MediaDeviceInfo — по одному на каждое устройство ввода звука, вывода звука и ввода видео, которое операционная система предоставляет браузеру. Каждый объект несёт четыре поля:
kind—"audioinput","audiooutput"или"videoinput"deviceId— непрозрачный идентификатор устройства, ограниченный источникомgroupId— непрозрачный идентификатор, общий для устройств одного физического оборудованияlabel— человекочитаемое имя устройства, например «FaceTime HD Camera» или «Logitech BRIO»
Важно, что сам вызов не требует разрешения на камеру или микрофон — только getUserMedia(). Страница может вызвать enumerateDevices() при загрузке, ещё до того, как пользователь хоть с чем-то взаимодействовал, и сразу узнать, сколько аудио- и видеоустройств есть на машине.
Разница до и после выдачи разрешения
Спецификация намеренно обнуляет deviceId, groupId и label, пока вызывающий источник хотя бы раз не получил доступ к камере или микрофону — это уступка производителей браузеров риску фингерпринтинга. Но обнуление частичное, а не полное, и разница между двумя состояниями сама по себе информативна.
async function getMediaDeviceFingerprint() {
const devices = await navigator.mediaDevices.enumerateDevices();
const counts = { audioinput: 0, audiooutput: 0, videoinput: 0 };
const labeled = [];
for (const d of devices) {
counts[d.kind]++;
if (d.label) {
// Заполняется только после того, как источник хотя бы раз получил разрешение getUserMedia()
labeled.push({ kind: d.kind, groupId: d.groupId, label: d.label });
}
}
return { counts, total: devices.length, labeled };
}
До разрешения: каждый deviceId и label — пустая строка, но devices.length и разбивка по kind точны. Машина с одним встроенным микрофоном, одной встроенной камерой и подключённой по Bluetooth гарнитурой сообщает иные данные, чем голый ноутбук без каких-либо подключённых устройств — и это количество остаётся стабильным на каждом сайте, который посещает пользователь, работая как низкокардинальный, но устойчивый межсайтовый сигнал.
После разрешения: метки и ID заполняются сразу, и они, как правило, раскрывают больше, чем просто общее название. Строки производителя и модели («Logitech BRIO», «iPhone Microphone») встречаются часто, а значения deviceId остаются стабильными для этого источника между сессиями — фактически становясь полуперсистентным идентификатором, привязанным к сайту, что схоже с тем, как персистентные идентификаторы посетителей переживают очистку кэша благодаря другим стабильным сигналам.
groupId: сопоставление физического оборудования
groupId существует для того, чтобы приложения могли избежать выбора микрофона и динамика, которые вызвали бы акустическую обратную связь из-за того, что они относятся к одному физическому устройству — но заодно он служит ключом для сопоставления. Два элемента MediaDeviceInfo с одинаковым groupId гарантированно принадлежат одному оборудованию, что позволяет скрипту сделать вывод вроде «эта камера и этот микрофон — часть одной USB веб-камеры», даже не читая строку модели. В сочетании с количеством устройств и любыми доступными метками groupId уточняет отпечаток, исключая комбинации устройств, которые физически не могли бы существовать вместе.
Количество устройств как межсайтовый сигнал
Перечисление устройств необычно среди техник фингерпринтинга тем, что не требует ни рендерингового конвейера, ни GPU, ни измерений времени — это прямое, структурированное считывание физического инвентаря оборудования. В сочетании с такими сигналами, как вывод WebGL и Canvas, количество медиаустройств добавляет ещё одну независимую ось: двух посетителей с одинаковыми GPU и одинаковыми хешами Canvas всё равно можно различить, если у одного есть динамик внешнего монитора и массив микрофонов веб-камеры, которых нет у другого. Это тот же принцип комбинирования, что описан в нашем руководстве по фингерпринтингу браузера — ни один отдельный сигнал не решает всё, но независимые сигналы перемножаются.
Есть и специфическое взаимодействие с WebRTC, которое стоит отметить отдельно: сайты, которые уже запрашивают доступ к камере/микрофону ради легитимной функции (видеочат, голосовые сообщения), получают полностью размеченный список устройств «бесплатно», превращая функциональную выдачу разрешения в устойчивый и необычно подробный вклад в фингерпринтинг.
Способы защиты
- Отклоняйте доступ к камере/микрофону по умолчанию и выдавайте его по сайтам только тогда, когда действительно собираетесь пользоваться функцией — это оставляет
deviceIdиlabelпустыми на сайтах, которым они не нужны. - Настройки Firefox
media.navigator.enabledи связанные с ними, а также приватные браузеры, построенные на принципе унификации (Tor Browser), ограничивают или полностью блокируют перечисление для большинства источников — ценой поломки легитимных функций видеозвонков через WebRTC. - Менеджеры разрешений браузера — просмотр и отзыв устаревших разрешений на камеру/микрофон (
chrome://settings/content/cameraи аналоги в Firefox/Safari) сужают набор источников, которые вообще могут видеть размеченные устройства. - Отключение неиспользуемой периферии снижает энтропию, связанную с количеством устройств, хотя это непрактично для большинства людей и закрывает лишь одну из многих осей.
Ни одна из этих мер не устраняет полностью сигнал о количестве устройств до выдачи разрешения, поскольку полная блокировка enumerateDevices() ломает любой сайт, которому нужен селектор устройств — реалистичная цель состоит в том, чтобы ограничить, сколько источников доходит до более богатого состояния после выдачи разрешения.
Чтобы увидеть, какие сигналы фингерпринтинга уже раскрывает ваш собственный браузер — Canvas, WebGL, шрифты и другие, — запустите проверку отпечатка от BrowserInsight и оцените, насколько узнаваемым они вас делают.
Часто задаваемые вопросы
Требует ли enumerateDevices() разрешения на камеру или микрофон?
Нет. Сам вызов работает без каких-либо запросов и сразу возвращает количество и тип устройств. Только поля deviceId, groupId и label требуют предварительного разрешения getUserMedia() от этого источника, чтобы заполниться.
Может ли сайт узнать, сколько у меня камер или микрофонов, не спрашивая?
Да. devices.length и разбивка по kind доступны ещё до появления любого диалога с запросом разрешения, и они сохраняются между визитами, поскольку отражают подключённое оборудование, а не что-то, что пользователь сбрасывает очисткой куки.
Сбрасывает ли очистка куки мой фингерпринт медиаустройств?
Нет. Количество устройств и, после выдачи разрешения, значения deviceId/groupId производятся из оборудования и состояния разрешений по источнику, а не из хранилища, которое удаляется очисткой куки — похоже на то, как фингерпринты Canvas и WebGL из руководства по фингерпринтингу браузера переживают очистку кэша.
Используется ли фингерпринтинг медиаустройств на реальных сайтах?
Библиотеки фингерпринтинга обычно включают количество устройств как один из многих сигналов, а у любого сайта с легитимной функцией видеозвонков или записи голоса уже есть доступ к полностью размеченному списку после выдачи разрешения, независимо от того, использует ли он эти данные для отслеживания.

