Межсайтовое определение входа использует тайминги, ошибки и редиректы, чтобы понять, куда вы вошли — как это работает и как браузеры это блокируют.
Ключевые выводы
- Статус входа — это побочный канал. Политика единого источника (Same-Origin Policy) не даёт сайту читать ваши куки или данные сессии на другом origin, но она никогда не обещала скрывать сам факт, что поведение ресурса отличается из-за того, что вы вошли где-то ещё.
- Проверки через ресурсы и тайминги используют кэширование и время загрузки: URL, который загружается быстро (или вообще загружается) только при активной сессии, раскрывает один бит информации при каждом запросе со страницы атакующего.
- Определение через редиректы и ошибки отслеживает события
onload/onerrorили отчёты о нарушении CSP, вызванные скрытым<img>,<script>или<link>, указывающим на защищённый авторизацией эндпоинт — сессии с входом и без него дают разные, наблюдаемые результаты. - Что это раскрывает: сопоставление этого сигнала по десяткам сайтов выстраивает профиль того, каким банком, медицинским порталом, форумом или сервисом знакомств вы пользуетесь — даже не зная вашего имени, что делает эту технику особенно ценной для целевого фишинга.
- Блокировка сторонних куки, разделение хранилища и разделение кэша закрывают большую часть этих утечек. Как только браузер перестаёт прикреплять куки сессии сайта к межсайтовым запросам, проверяемый ресурс всегда выглядит «не авторизованным», независимо от вашего реального статуса. Chrome — частичное исключение: кэш и хранилище он разделяет по умолчанию, но сторонние куки продолжает отправлять, пока вы сами не включите блокировку.
Что такое межсайтовое определение статуса входа
Межсайтовое определение статуса входа относится к классу побочных каналов браузера, известных как XS-Leaks (межсайтовые утечки). Политика единого источника отлично справляется с одной конкретной задачей: не даёт attacker.example прочитать содержимое ответа от bank.example. Но она никогда не была рассчитана на то, чтобы помешать attacker.example запросить этот ответ и понаблюдать за поведением запроса — а одного поведения зачастую достаточно, чтобы ответить на вопрос вроде «авторизован ли сейчас этот посетитель на bank.example».
Минимальный вариант выглядит так: страница атакующего встраивает скрытый тег <img>, чей src указывает на https://bank.example/account/avatar.png. Если вы вошли в этот банк, браузер автоматически прикрепляет куки сессии, сервер возвращает настоящее изображение, срабатывает событие load. Если вы не вошли, тот же запрос перенаправляется на страницу входа или отклоняется, ответ не является валидным изображением — и вместо этого срабатывает error. Атакующий вообще не видит деталей вашего аккаунта — ему нужно лишь узнать, какое событие сработало. Обзор межсайтовых утечек (XS-Leaks) на MDN относит эту и описанные ниже техники к отдельной, постоянно актуальной категории уязвимостей браузерной безопасности, а не к разовому эксплойту.
Проверки через ресурсы и тайминги против известных эндпоинтов
Приведённая выше версия на основе событий — самая грубая. Более тонкое семейство техник опирается не на чёткий сигнал «успех/неудача», а на тайминги. Браузеры предоставляют Resource Timing API (performance.getEntriesByType('resource')), который сообщает, сколько времени занял запрос, а для ресурсов с того же origin или разрешённых через Timing-Allow-Origin — ещё и сколько байт было передано. Это открывает два пути атаки:
- Проверки через тайминг кэша. Если ресурс запрашивается — и, соответственно, кэшируется — только пользователями, которые активно используют целевой сайт (например, JS-бандл, доступный лишь после входа, или API-ответ, встроенный в страницу только для авторизованных), то последующий запрос со страницы атакующего для таких пользователей практически мгновенно попадёт в кэш, а для остальных уйдёт в сеть. Одной только разницы во времени достаточно, чтобы ответить на вопрос о статусе входа.
- Подсчёт фреймов и запросов. Некоторые страницы, доступные только после входа, рендерят больше подресурсов, чем их неавторизованный аналог (дополнительные виджеты, персонализированные модули, дополнительные редиректы). Подсчёт того, сколько запросов или фреймов вызывает загрузка страницы, позволяет различить эти два состояния без чтения тела ответа. Свойство
window.length— количество фреймов внутри окна — одно из немногих, которые платформа намеренно оставляет читаемыми между origin, поэтому атакующий, открывший цель во всплывающем окне или в iframe, узнает число её фреймов, хотя все остальные детали этого документа для него закрыты.
Ни один из этих способов не требует JavaScript-доступа к целевому origin: всё, что измеряет атакующий, происходит в контексте выполнения его собственной страницы — именно поэтому такие техники так долго оставались незамеченными: с точки зрения браузера это не выглядит нарушением политики единого источника.
Определение через редиректы и ошибки
Вариант с редиректами развивает ту же идею дальше. Вместо того чтобы полагаться только на onerror, атакующий может сопроводить запрос строгой политикой Content-Security-Policy, разрешающей соединения только с ожидаемым origin ресурса, а затем слушать событие securitypolicyviolation. Если неавторизованный запрос перенаправляется на страницу входа, размещённую на другом origin, CSP блокирует этот редирект и вызывает нарушение, которое атакующий может отследить. Авторизованный запрос, который сразу возвращает ресурс без редиректа, такое нарушение не вызывает. Наличие или отсутствие этого единственного события и становится «оракулом» — эта техника входит в число паттернов XS-Leak на основе событий ошибок и CSP, которые исследователи безопасности каталогизировали на множестве сайтов за последние годы.
Устойчивость атаки через редиректы объясняется тем, что она не зависит от какой-то конкретной ошибки в коде целевого сайта — она зависит лишь от того, что целевой сайт где-то в цепочке ответа ведёт себя по-разному для авторизованных и неавторизованных посетителей, а это верно почти для любого сервиса с барьером аутентификации.
Что это раскрывает о вас
Один ответ «да/нет» — «авторизован на bank.example: да» — сам по себе не особенно чувствителен. Риск нарастает, когда атакующий прогоняет ту же проверку по списку из десятков или сотен известных сайтов: банки, медицинские порталы, приложения знакомств, форумы, связанные с конкретными политическими или религиозными сообществами, сайты для взрослых, корпоративные интранеты. Получившаяся битовая карта «авторизован/не авторизован» — это поведенческий отпечаток, отличный от отпечатка устройства, описанного в нашем руководстве по браузерным отпечаткам, и зачастую более чувствительный, потому что он напрямую называет какими сервисами вы пользуетесь, а не просто сужает круг возможных устройств.
Такой профиль немедленно полезен для целевого фишинга: письмо, в котором верно назван ваш реальный банк, а не общее «Сбер или Альфа-Банк, угадайте сами», выглядит куда убедительнее. Это и самостоятельный вектор деанонимизации — конкретная комбинация сервисов, в которые вошёл человек, может идентифицировать почти так же точно, как отпечаток устройства, но без технического шума, присущего показаниям canvas или WebGL.
Защита в браузерах: разделение, блокировка куки и Fetch Metadata
Структурное решение — не латать отдельные уязвимые эндпоинты (у сайта их могут быть тысячи), а убрать браузерное поведение, от которого зависит большинство этих техник: межсайтовый запрос незаметно уносит с собой куки сессии целевого сайта.
Именно это делают блокировка сторонних куки и разделение хранилища. Total Cookie Protection в Firefox выдаёт каждому стороннему ресурсу собственную «банку» куки, привязанную к сайту верхнего уровня, поэтому куки сессии bank.example попросту не прикрепляются, когда их запрашивает attacker.example — проверяемый ресурс безусловно выглядит «не авторизованным», независимо от вашего реального состояния сессии. Полная блокировка сторонних куки в Safari достигает того же результата по умолчанию, а не через ключ раздела. Chrome — частичное исключение, о котором стоит знать: он тоже выпустил собственное разделение хранилища плюс опциональный механизм CHIPS для тех легитимных случаев (встроенные виджеты, балансировка нагрузки через CDN), которым по-прежнему нужно немного межстраничного состояния, но не межсайтовое отслеживание, — однако после нескольких переносов Google отказался от плана отключить сторонние куки для всех. Обычное окно Chrome по-прежнему их прикрепляет; окно в режиме инкогнито — блокирует.
Впрочем, дело не только в куки. Проверке через тайминг кэша куки на самом запросе-зонде вообще не нужны — она читает запись в кэше, оставшуюся от вашего прежнего, уже авторизованного визита. Здесь работает отдельный механизм: браузеры разделяют HTTP-кэш по сайту верхнего уровня, поэтому ресурс, закэшированный, пока вы были на bank.example, хранится под другим ключом, чем тот же URL, запрошенный со страницы на attacker.example, — и запрос атакующего гарантированно промахивается мимо кэша. Chrome и Firefox выпустили это в 2020–2021 годах, а WebKit разделил сетевое состояние ещё раньше; именно поэтому кэш-зонды перестали работать даже в браузерах, которые по умолчанию всё ещё отправляют сторонние куки.
Дополняющая серверная защита — заголовки Fetch Metadata, в частности Sec-Fetch-Site. Поскольку эти заголовки генерируются браузером и не могут быть подделаны из JavaScript, сервер может проверить наличие Sec-Fetch-Site: cross-site в запросе к чувствительному, защищённому авторизацией эндпоинту и просто отказать в обслуживании — закрывая утечку у источника данных, независимо от того, каким браузером пользуется посетитель. Эти две защиты усиливают друг друга: разделение на стороне браузера защищает вас даже на сайтах, ещё не внедривших Fetch Metadata, а Fetch Metadata защищает посетителей на браузерах, которые пока по умолчанию отправляют сторонние куки.
Собираем воедино
Межсайтовое определение статуса входа — близкий родственник пассивных, управляемых событиями техник наблюдения, описанных в нашем руководстве по детекции ботов: обе полагаются на наблюдение за таймингами загрузки, событиями ошибок и поведением ответа, а не на прямое чтение того, что видеть не положено. И она не заменяет отпечаток устройства, а усиливает его: знание того, какое устройство и какие аккаунты указывают на одного и того же посетителя — более сильный сигнал, чем каждый по отдельности, поэтому понимание того, как складываются сигналы отпечатка, важно и здесь, даже если конкретная утечка касается статуса входа, а не характеристик оборудования.
Хорошая новость в том, что это одна из немногих техник слежения, где решение действительно лежит в браузере, а не в ваших собственных привычках. В актуальном Firefox или Safari с включёнными по умолчанию защитами описанные выше проверки, зависящие от куки, уже мертвы: куки сессии, на которые они опираются, никогда не покидают установивший их сайт. В Chrome проверки через тайминг кэша тоже закрыты по умолчанию, а вот основанные на куки остаются рабочими, пока вы сами не включите блокировку сторонних куки, — что делает эту единственную настройку самым полезным действием против всего класса таких утечек.
Сам статус входа страница показать вам не может: проверка выполняется на чужом сайте, и по самой её конструкции вы никогда не видите ответа. Зато вы можете осмотреть остальную поверхность, с которой атакующий этот сигнал сопоставит: проверка отпечатка от BrowserInsight показывает сигналы canvas, WebGL, шрифтов и хранилища, которые ваш браузер раскрывает прямо сейчас, — всё измеряется в вашем браузере и никуда не отправляется для анализа.
Часто задаваемые вопросы
Может ли сайт действительно узнать, что я вошёл в свой банк, просто по факту посещения страницы?
С помощью описанных выше техник раньше это было вполне надёжно возможно. В браузере, который блокирует сторонние куки по умолчанию — а актуальные Firefox и Safari делают именно так, — куки сессии вашего банка больше не прикрепляются к межсайтовому запросу, поэтому проверяемый ресурс выглядит «не авторизованным» независимо от вашей реальной сессии, и банку не нужно ничего менять. Оставшийся пробел — Chrome: разделение кэша и хранилища включено по умолчанию и убивает проверки через тайминги, но сторонние куки по-прежнему отправляются, пока вы не включите блокировку в настройках приватности или не откроете окно инкогнито.
Останавливает ли режим приватного/инкогнито-просмотра определение статуса входа?
В основном да, но по косвенной причине: в приватном окне вы обычно никуда не авторизованы, поэтому любая проверка честно отвечает «не авторизован». В остальном приватный режим правил не меняет — внутри активной приватной сессии поведение межсайтовых куки зависит от тех же настроек блокировки сторонних куки и разделения, что и в обычном режиме. Исключение, о котором стоит знать, — Chrome: в инкогнито он блокирует сторонние куки по умолчанию, а в обычном окне нет, так что здесь приватное окно действительно закрывает проверки, которые обычное оставляет открытыми.
Это то же самое, что и снятие браузерного отпечатка?
Нет, и разница важна. Снятие отпечатка идентифицирует ваше устройство по техническим атрибутам вроде шрифтов, вывода GPU и геометрии экрана. Определение статуса входа идентифицирует, в какие аккаунты вы вошли, опираясь на то, как браузер обращается с куки, а не на какой-либо атрибут устройства. Атакующий, владеющий обоими сигналами, способен связать конкретное устройство с конкретным набором реальных сервисов — вместе оба сигнала намного мощнее, чем каждый по отдельности.
Может ли сайт решить эту проблему сам, не дожидаясь изменений в браузерах?
Отчасти. Внедрение заголовков Fetch Metadata позволяет сайту напрямую отклонять межсайтовые запросы к защищённым авторизацией эндпоинтам, что закрывает утечку для каждого посетителя независимо от браузера. Но это требует, чтобы конкретный сайт корректно реализовал это на каждом чувствительном эндпоинте, поэтому разделение на стороне браузера — защищающее посетителей даже на сайтах, не проделавших эту работу, — стало основной линией обороны.


