Cookies, хранилища, 3 474 запроса, браузерный отпечаток и WebGPU: технический аудит веб-сессии
С вами Чайка, Head of Innovation в нескольких крупных командах. Мой фокус: искать новые источники трафика и стабилизировать уже работающие. В последний месяц баеры все чаще спрашивают, почему так штормит. Поэтому мы решили заглянуть под капот самого ФБ*: разобраться, как он вас палит и чем это грозит.
В этой статье разберем, что браузер помнит, что хранит и с кем разговаривает, пока вы думаете, что просто смотрите на кабинет. А затем посмотрим, что он рассказывает странице о самом устройстве.
Современный Facebook* это не страница. Это огромное JavaScript-приложение, которое продолжает жить в вашем браузере: читает состояние, создает новое, обращается к локальному хранилищу, ходит в сеть и снова обновляет состояние. Все это без перезагрузки и без единого вашего клика.
Мы продолжили аудит той же веб-сессии с помощью Frida и MITM-прокси. Цифры за одну сессию:
|
Показатель |
Значение |
|
Сетевых запросов |
3 474 |
|
Сторонних доменов |
55 |
|
Операций Web Storage (категория DET-050) |
1 895 |
|
Вызовов IndexedDB |
10 |
Но цифры ничего не значат без ответа на вопрос: что именно за ними стоит? Идем слой за слоем.
Cookie — небольшая запись, которую браузер хранит для сайта и автоматически прикладывает к запросам. Выглядит просто (name=value), но вся сила в атрибутах:
Domain · Path · Expires · Max-Age · Secure · HttpOnly · SameSite
Они определяют, где и когда cookie доступна и куда уйдет. Secure разрешает передачу только по защищенному соединению. HttpOnly закрывает cookie от JavaScript. SameSite управляет отправкой в межсайтовых запросах.
В нашем аудите Facebook* обращался к cookie сразу несколькими способами. Фиксировался классический , а также методы современного Cookie Store API: get(), getAll(), set() и delete().
Вот что здесь принципиально важно для арбитражника. Если у cookie стоит флаг HttpOnly, JavaScript ее не увидит. Но браузер все равно отправит ее серверу вместе с запросом.
Сервер видит cookie, которых не видит ни скрипт на странице, ни вы, если проверяете только ``.
Поэтому «я почистил куки» часто означает «я почистил то, что видно». А полный набор состояния профиля гораздо шире.
Cookies — не единственная память. Приложение само записывает туда данные (setItem), читает (getItem), удаляет (removeItem, clear).
Разница в жизненном цикле. Переживает перезапуск браузера. Привязан к конкретной вкладке и ее сессии. Для сайта это возможность хранить часть состояния не на сервере, а у вас.
Storage API мы выделили в отдельную категорию DET-050. В одном из разделов записи счетчик дошел до 1 895 операций.
Это не 1 895 разных параметров. Одна и та же запись читается снова и снова: приложение проверило состояние, изменило, прочитало заново, сохранило. Но вывод от этого не слабее: Facebook* работает с локальным хранилищем непрерывно. Его состояние в вашем браузере не декорация, а рабочая часть приложения.
И это ровно то, что нужно помнить при настройке профиля: хранилище не должно быть пустой формальностью.
Для сложных структур у браузера есть IndexedDB. Это полноценная база данных: объекты, индексы, хранилища, асинхронная работа. Приложение может держать в ней локальный кэш, настройки и служебные данные.
В аудите зафиксированы три вызова:
Всего в этой группе 10 вызовов.
Самый интересный здесь databases(). Он позволяет странице спросить браузер: какие базы у тебя уже есть? Профиль, который живет давно, и профиль, созданный пять минут назад, отвечают на этот вопрос по-разному.
Свежий «стерильный» профиль и профиль с историей выглядят для страницы разными устройствами. Даже если и User-Agent, и Canvas у них идентичны.
После загрузки страницы работа только начинается. JavaScript отправляет запросы, получает ответы, подгружает компоненты, обновляет данные, отправляет результаты ваших действий. Поэтому в одной сессии набегает почти три с половиной тысячи запросов.
Это скрипты, стили, картинки, шрифты, данные интерфейса, аналитика, API-запросы и библиотеки. Часть ресурсов приходит с 55 сторонних доменов: CDN, API, аналитика, хранилища, рекламная инфраструктура.
Для вас здесь два практических вывода:
Мы уже касались Client Hints в первой части. В сетевом слое они проявляются через заголовок Accept-CH: так сервер сообщает браузеру, какие дополнительные характеристики ему нужны. В аудите фигурировали dpr, sec-ch-prefers-color-scheme, sec-ch-ua-full-version-list, sec-ch-ua-model, sec-ch-ua-platform-version и viewport-width.
Вывод тот же, но с практическим весом: все эти значения должны быть согласованы с вашим User-Agent. Если в UA вы Windows, а sec-ch-ua-platform-version или sec-ch-ua-model рассказывают другую историю, это расхождение, которое создаете вы сами.
В наблюдаемых заголовках присутствовал Permissions-Policy. Через него сайт задает правила: какие возможности браузера доступны странице, а какие встроенным iframe и сторонним компонентам. Сервер не только получает данные от браузера, но и управляет тем, что разным частям страницы разрешено.
В логах встретились Report-To и Reporting-Endpoints. Это инфраструктура браузерных отчетов: сервер объявляет браузеру адрес, куда тот может отправлять отчеты о событиях (ошибки, нарушения политик и подобное).
Для нас это еще один пример того же принципа: у браузера есть собственный канал связи с сервером, который работает параллельно с обычными запросами страницы.
Отдельная история: WebRTC. Это технология передачи аудио, видео и данных в реальном времени (звонки, конференции, peer-to-peer). Для установления соединения она работает с сетевой информацией: прежде всего с ICE-кандидатами, то есть возможными маршрутами соединения.
В ходе аудита обращения к WebRTC API зафиксированы. Данные самих ICE-кандидатов мы из анализа исключили. Поэтому мы не называем конкретный IP-адрес, который мог получить Facebook*. Мы фиксируем другое: механизм присутствует в сессии, и страница к нему обращается.
Для арбитражника этого достаточно, чтобы поставить WebRTC в чек-лист. Потому что именно этот механизм исторически работает мимо настроек прокси, и на нем чаще всего ломаются профили, где «IP вроде бы красивый».
По отдельности каждый элемент выглядит рутинным. Cookie — механизм HTTP. и IndexedDB — стандарт. Запросы — основа веба. Но вместе они образуют живой цикл, который повторяется тысячи раз за сессию:
Браузер → локальное состояние → JavaScript → сетевой запрос
↑ ↓
новое состояние ←←←←←←←←←←←← ответ сервера
И в этом цикле работает главный принцип всей серии: один параметр почти ничего не значит. Значит комбинация и ее согласованность во времени.
Мы не заглядываем в серверную часть Meta* и не утверждаем, как именно там принимаются решения. Но то, что происходит в браузере, мы видели своими глазами. И из этого следуют вполне конкретные вещи для настройки профиля:
|
Слой |
Что мы увидели |
Что проверить в профиле |
|
Cookies |
и Cookie Store API; HttpOnly скрыты от JS, но уходят на сервер |
Не чистить «на глаз»; куки должны соответствовать истории профиля, а не копироваться вслепую |
|
Web Storage |
1 895 операций в категории DET-050 |
Хранилище должно сохраняться между запусками и не обнуляться при каждом старте |
|
IndexedDB |
open(), deleteDatabase(), databases() |
Профиль не должен каждый раз выглядеть как только что созданный |
|
Client Hints |
Accept-CH: model, platform-version, full-version-list, dpr, viewport-width |
Согласованность с UA, платформой и размером окна |
|
Сеть |
3 474 запроса, 55 сторонних доменов |
Прокси, DNS, часовой пояс и язык должны рассказывать одну историю |
|
WebRTC |
Обращения к API зафиксированы |
Режим WebRTC в антидетекте настроен и проверен на утечки |
Профиль — это не набор подмененных значений. Это живая система, в которой все должно быть согласовано: железо, сеть, хранилища и поведение.
Но это только половина истории. Вторая половина: что браузер рассказывает о самом себе и о железе, на котором запущен.
Давайте честно: кто из арбитражников за последние годы не мечтал открыть Facebook*, запустить рекламу и просто спокойно работать? Без внезапных проверок, без смены правил игры, без попыток понять, почему вчера всё лилось, а сегодня привычный сценарий уже не работает.
Facebook* остается одним из главных источников рекламного трафика, но предсказуемости в нем с каждым годом меньше. Меняются инструменты, проверки и требования к рекламодателям. Поэтому логичный вопрос: что именно Facebook* видит, когда мы открываем его в браузере?
Обычно разговор сводится к User-Agent, разрешению экрана, языку и часовому поясу. Знакомый набор. Но мы решили заглянуть глубже.
Мы провели технический аудит веб-сессии с помощью Frida и MITM-прокси и зафиксировали, какие механизмы Facebook* задействует прямо во время работы. Аудит одной сессии не раскрывает всю серверную кухню Meta*, но то, что мы увидели на стороне браузера, говорит само за себя. Facebook* интересуется не только названием и версией вашего браузера. Страница обращается к целому набору возможностей устройства, на котором этот браузер запущен.
В ходе аудита зафиксированы обращения к свойствам navigator:
Каждый параметр играет свою роль. userAgent сообщает браузер, версию и совместимость. platform описывает платформу, vendor указывает на производителя браузера. appVersion и appName — исторический багаж, их информативность сегодня невелика.
language и languages выдают основной язык и список предпочитаемых. Это часть описания среды, и Facebook* их запрашивает.
Дальше идут параметры, уже близкие к железу. hardwareConcurrency возвращает число логических процессоров. Это не всегда число физических ядер, но для сравнения профилей достаточно. deviceMemory отдает приблизительную категорию объёма оперативной памяти.
Каждое значение по отдельности выглядит безобидно. Но язык, платформа, версия браузера, число потоков и объем памяти в сумме дают довольно подробный портрет устройства. Именно из таких комбинаций и строится техническая идентификация.
Client Hints — современный механизм, который передает характеристики устройства через HTTP-заголовки. В трафике мы зафиксировали запросы через заголовок Accept-CH, то есть сервер прямо просит у браузера дополнительные данные.
В наблюдениях фигурировали такие параметры:
|
Параметр |
Что означает |
|
dpr |
Соотношение пикселей устройства к CSS-пикселям |
|
sec-ch-prefers-color-scheme |
Предпочтение светлой или тёмной темы |
|
sec-ch-ua-full-version-list |
Бренды и полные версии браузера |
|
sec-ch-ua-model |
Модель устройства |
|
sec-ch-ua-platform-version |
Версия платформы |
|
viewport-width |
Ширина области просмотра |
Важный момент: данные о среде уходят не только через JavaScript на странице, но и через заголовки запросов. Профиль браузера — это гораздо больше, чем одна строка User-Agent. Если вы настроили только ее, остальное продолжает говорить за вас.
Canvas — один из самых известных механизмов идентификации. Это HTML-элемент для рисования графики прямо в браузере.
В ходе аудита зафиксирован вызов CanvasRenderingContext2D.getImageData(). В нашей классификации он обозначен как DET-002, и мы зафиксировали два таких события.
Метод читает пиксели из заданной области холста и возвращает массив цвета и прозрачности. А результат отрисовки зависит от браузера, графического стека, операционной системы и драйверов. Одно и то же изображение на разных машинах рендерится с микроразличиями, и по ним среду можно различать.
Facebook* обращался к этому методу, так что Canvas в его арсенале есть.
Есть слой информации, о котором легко забыть. Страница измеряет собственные элементы через getBoundingClientRect(), getClientRects(), offsetWidth и offsetHeight.
Так определяются размеры блоков, видимость контента и реальный размер отрисованного текста, а он зависит от установленных шрифтов и их рендеринга. Поэтому геометрия интерфейса — еще один источник данных о вашей системе, и Facebook* активно к нему обращается.
Если копнуть глубже, выяснится, что браузер умеет работать с графикой не только через Canvas. Есть технология, о которой за пределами разработчиков и специалистов по отпечаткам слышали немногие, — WebGPU.
WebGPU — современный интерфейс для работы с графическим процессором. Он дополняет WebGL и умеет заметно больше: веб-приложения получают прямой доступ к возможностям видеокарты для графики и вычислений.
В ходе аудита зафиксирован вызов requestAdapter(): страница запросила у браузера графический адаптер. А это значит, что она получает сведения о поддерживаемых функциях и лимитах вашего GPU.
Зачем это Facebook*? GPU не абстрактная деталь. Устройство, драйверы, операционная система и реализация браузера определяют, какие графические возможности доступны и как выполняются операции. Поэтому WebGPU дает еще один слой данных о реальном железе.
А теперь главное. Если браузер заявляет «RTX 2080», а WebGPU видит среду без нормального GPU, для антифрода это очень интересное несоответствие. И триггерная галочка в ваш адрес.
Именно поэтому в антидетекте мало закрыть Canvas и WebGL. Графическая подсистема должна быть согласована целиком.
О звуке при разговоре об отпечатках вспоминают реже всего. Но у браузера есть Web Audio API, а его центральный компонент — AudioContext.
Представьте аудиоредактор, работающий прямо в браузере: он генерирует сигналы, пропускает их через фильтры и эффекты, обрабатывает аудиоданные.
В ходе аудита зафиксированы вызовы OfflineAudioContext: страница выполняла звуковые вычисления в фоновом режиме, без единого звука для пользователя. Результат таких вычислений зависит от процессора, системы и реализации браузера, а значит, по нему можно отличить одно устройство от другого.
На данный момент пул аудио-отпечатков, соответствующих Windows, составляет 4096 вариантов. Мы не можем просто сгенерировать случайное значение. Оно должно соответствовать реальному процессору, иначе отпечаток будет распознан как фейковый.
Начнем с того, что аудио-отпечаток не один. Их пять.
Тот, что вы привыкли видеть и который подменяют в антидетектах, — классический вариант, получивший широкое распространение. Он называется Triangle, по треугольной форме волны. Именно его мы видим на большинстве сайтов проверки отпечатков.
Остальные четыре:
Сайт может применить любой из пяти. Если вы подменили только Triangle, остальные четыре продолжают отдавать реальные значения или значения, которые не согласуются с подмененным. Такое расхождение и выдает антидетект.
В ходе аудита мы зафиксировали вызовы requestAdapter() (WebGPU) и OfflineAudioContext (AudioContext). Facebook* обращается к обоим интерфейсам и использует их для антифрод-проверок. Это логично: соцсети и рекламные платформы борются с мультиаккаунтингом, и им критично отличать реальные устройства от подменённых окружений.
Тут и проявляется главная сложность. Настройки браузера видны и легко меняются, а результаты работы API определяются железом, драйверами и системой. Сайту достаточно выполнить несколько вычислений, чтобы получить характерный след. Если антидетект закрывает Canvas и WebGL, но забывает про аудио и WebGPU, это расхождение становится поводом для срабатывания антифрода.
Подменять нужно всю поверхность целиком, а не несколько знакомых параметров.
Современная веб-сессия — это не название браузера и одна-две настройки профиля. В наблюдаемой среде несколько уровней:
Каждый механизм по отдельности можно объяснить безобидной причиной. Но вместе они складываются в подробный технический портрет среды. И его несоответствия, например браузер заявляет одно, а железо показывает другое, становятся сигналом для антифрода.
В следующей статье идем еще глубже: Performance API, геометрия DOM, Network Information, Media Capabilities, Pointer/Mouse/Idle API и WebAssembly. Там страница наблюдает уже не только за состоянием, но и за тем, как ведет себя браузер и человек за ним.
Нора продолжается.
Ваш Чайка подписывайтесь на БЛОГ.
Мой контакт в Телеге @chaika_inc.
* — принадлежит компании Meta, признанной экстремистской и запрещенной на территории РФ.
Удалось как то налить им, 900~ фтд где-то вышло. До сих пор даже капает. Может чуть позже еще вернусь снова по ревшаре полить, а то в кейсах у коллег уж больно сладкие RD, надо проверить.
Ребята аффы написали вчера говорят вернуться траф дать)) ну отзыв попросили оставить, мне в целом не сложно, но вот как есть)