Обход ограничений SameSite для cookie

SameSite — это механизм безопасности браузера, который определяет, когда cookie веб-сайта включаются в запросы, исходящие с других сайтов. Ограничения SameSite для cookie обеспечивают частичную защиту от различных Cross-Site атак, включая CSRF (межсайтовая подделка запросов), cross-site leaks (кросс-сайтовые утечки) и некоторые эксплойты CORS.

С 2021 года Chrome применяет ограничения Lax по умолчанию, если сайт, устанавливающий cookie, явно не указывает желаемый уровень ограничений. Это предлагаемый стандарт, и мы ожидаем, что другие основные браузеры в будущем примут такое поведение. В результате крайне важно хорошо понимать, как работают эти ограничения и как их потенциально можно обойти, чтобы полноценно тестировать Cross-Site векторы атак.

В этом разделе мы сначала рассмотрим, как работает механизм SameSite, и проясним связанную терминологию. Затем посмотрим на некоторые из самых распространённых способов обхода этих ограничений, позволяющих осуществлять CSRF и другие Cross-Site атаки на сайтах, которые поначалу могут казаться защищёнными.

В контексте ограничений SameSite «Site» определяется как домен верхнего уровня (TLD), обычно что-то вроде .com или .net, плюс ещё один уровень доменного имени. Это часто называют TLD+1.

При определении того, является ли запрос same-site или нет, также учитывается схема URL. Это означает, что ссылка с http://app.example.com на https://app.example.com большинством браузеров рассматривается как Cross-Site.

site-definition

Note

Вы можете встретить термин «effective top-level domain» (eTLD, «эффективный домен верхнего уровня»). Это способ учёта зарезервированных составных суффиксов, которые на практике трактуются как домены верхнего уровня, например .co.uk.

В чём разница между «Site» и «Origin»?

Разница между Site и Origin — в их области действия: сайт охватывает несколько доменных имён, тогда как источник включает только одно. Хотя они тесно связаны, важно не использовать термины взаимозаменяемо, поскольку смешение понятий может иметь серьёзные последствия для безопасности.

Два URL считаются имеющими один и тот же origin, если у них совпадают схема, доменное имя и порт. Обратите внимание, что порт часто подразумевается исходя из схемы.

site-vs-origin.png

Как видно из примера, термин «Site» гораздо менее специфичен, так как учитывает только схему и последнюю часть доменного имени. Что особенно важно — Cross-Origin запрос всё ещё может быть same-site, но не наоборот.

Request from

Request to

Same-site?

Same-origin?

https://example.com

https://example.com

Да

Да

https://app.example.com

https://intranet.example.com

Да

Нет: несовпадающее доменное имя

https://example.com

https://example.com:8080

Да

Нет: несовпадающий порт

https://example.com

https://example.co.uk

Нет: несовпадающий eTLD

Нет: несовпадающее доменное имя

https://example.com

http://example.com

Нет: несовпадающая схема

Нет: несовпадающая схема

Это важное различие, поскольку оно означает, что любую уязвимость, позволяющую выполнять произвольный JavaScript, можно использовать для обхода защит на основе «Site» на других доменах, принадлежащих тому же сайту.

Как работает SameSite?

До появления механизма SameSite браузеры отправляли cookie в каждом запросе к домену, который их установил, даже если запрос был вызван несвязанным сторонним сайтом. + SameSite позволяет браузерам и владельцам сайтов ограничивать, какие Cross-Site запросы должны включать конкретные cookie. Это помогает снизить подверженность пользователей атакам CSRF, которые побуждают браузер жертвы отправить запрос, запускающий вредоносное действие на уязвимом сайте. Поскольку такие запросы обычно требуют cookie, связанного с аутентифицированной сессией жертвы, атака провалится, если браузер его не включит.

Все основные браузеры в настоящее время поддерживают следующие уровни ограничений SameSite:

Разработчики могут вручную настроить уровень ограничения для каждого устанавливаемого cookie, получая больший контроль над тем, когда эти cookie используются. Для этого достаточно включить атрибут SameSite в заголовок ответа Set-Cookie вместе с предпочтительным значением:

Хотя это и обеспечивает некоторую защиту от CSRF-атак, ни одно из этих ограничений не гарантирует полной невосприимчивости, что продемонстрированы на намеренно уязвимых интерактивных лабораторных работах далее в этом разделе.

Note

Если сайт, устанавливающий cookie, явно не указывает атрибут SameSite, Chrome автоматически применяет ограничения Lax по умолчанию. Это означает, что cookie отправляется только в Cross-Site запросах, отвечающих определённым критериям, даже если разработчики не настраивали такое поведение. Поскольку это предлагаемый новый стандарт, мы ожидаем, что другие основные браузеры в будущем примут такое поведение.

Strict

Если cookie установлен с атрибутом SameSite=Strict, браузеры не будут отправлять его ни в каких Cross-Site запросах. Проще говоря, если целевой сайт запроса не совпадает с сайтом, отображаемым в адресной строке браузера, cookie не будет включён.

Это рекомендуется при установке cookie, позволяющих их владельцу изменять данные или выполнять другие чувствительные действия, например доступ к определённым страницам, доступным только аутентифицированным пользователям.

Хотя это самый безопасный вариант, он может негативно повлиять на пользовательский опыт в случаях, когда желательна Cross-Site функциональность.

Lax

Ограничения SameSite Lax означают, что браузеры отправят cookie в Cross-Site запросах, но только если выполняются оба условия:

  • Запрос использует метод GET.

  • Запрос возник из навигации верхнего уровня (top-level navigation) пользователем, например при клике по ссылке.

Это означает, что cookie не включается, например, в Cross-Site POST-запросы. Поскольку POST-запросы, как правило, используются для выполнения действий, изменяющих данные или состояние (по крайней мере согласно лучшим практикам), они гораздо чаще становятся целью CSRF-атак.

Аналогично, cookie не включается в фоновый запрос, например инициированный скриптами, iframe или ссылками на изображения и другие ресурсы.

None

Если cookie установлен с атрибутом SameSite=None, это фактически полностью отключает ограничения SameSite вне зависимости от браузера. В результате браузеры будут отправлять такой cookie во всех запросах к сайту, который его установил, даже если они были вызваны совершенно несвязанными сторонними сайтами.

За исключением Chrome, это поведение по умолчанию, используемое основными браузерами, если при установке cookie не указан атрибут SameSite.

Существуют законные причины отключать SameSite, например когда cookie предназначен для использования в стороннем контексте и не предоставляет владельцу доступ к каким-либо чувствительным данным или функциональности. Типичный пример — трекинговые cookie.

Если вы сталкиваетесь с cookie, установленным с SameSite=None или без явных ограничений, имеет смысл проверить, не представляет ли он ценности. Когда поведение «Lax-by-default» впервые было принято в Chrome, это побочно сломало множество существующих веб-функций. В качестве быстрого обходного решения некоторые сайты решили просто отключить ограничения SameSite для всех cookie, включая потенциально чувствительные. При установке cookie с SameSite=None сайт также должен включить атрибут Secure, который гарантирует, что cookie будет отправляться только в зашифрованных сообщениях по HTTPS. Иначе браузеры отклонят cookie, и он не будет установлен.

Обход ограничений SameSite Lax с использованием GET-запросов

На практике серверы не всегда строго относятся к тому, получают ли они GET- или POST-запрос на конкретную конечную точку, даже если ожидается отправка формы. Если они также используют ограничения Lax для своих сессионных cookie — явно или из-за поведения браузера по умолчанию — у вас всё ещё может получиться провести атаку CSRF, вызвав GET-запрос из браузера жертвы.

Пока запрос происходит через навигацию верхнего уровня, браузер всё равно включит сессионный cookie жертвы. Ниже один из простейших подходов к запуску такой атаки:

Даже если обычный GET-запрос не допускается, некоторые фреймворки предоставляют способы переопределения метода, указанного в строке запроса. Например, Symfony поддерживает параметр _method в формах, который имеет приоритет над обычным методом для целей маршрутизации:

Другие фреймворки поддерживают различные похожие параметры.

Обход ограничений SameSite с использованием гаджетов (gadget) на сайте

Если cookie установлен с атрибутом SameSite=Strict, браузеры не включают его ни в какие Cross-Site запросы. Вы можете обойти это ограничение, если найдёте «гаджет (gadget)», который приводит ко вторичному запросу в рамках того же сайта.

Одним из возможных гаджетов является клиентский редирект (client-side redirect), который динамически формирует цель перенаправления на основе контролируемого злоумышленником ввода, например параметров URL. Несколько примеров см. в материалах об DOM-based open redirection.

С точки зрения браузеров такие клиентские редиректы на самом деле не считаются редиректами; получившийся запрос трактуется как обычный самостоятельный запрос. И что важнее всего — это запрос в рамках того же сайта, и, как таковой, он включит все cookie, связанные с сайтом, независимо от действующих ограничений.

Если вы можете манипулировать этим гаджетом так, чтобы он инициировал вредоносный вторичный запрос, это позволит полностью обойти любые ограничения SameSite для cookie. Обратите внимание, что эквивалентная атака невозможна при серверных редиректах. В этом случае браузеры распознают, что запрос на следование редиректу изначально возник из Cross-Site запроса, поэтому они всё равно применяют соответствующие ограничения для cookie.

Обход ограничений SameSite через родственные домены (sibling domains)

Независимо от того, тестируете ли вы чужой сайт или пытаетесь защитить свой, важно помнить, что запрос всё ещё может быть Same-Site, даже если он выполняется Cross-Origin.

Убедитесь, что вы тщательно аудируете всю доступную поверхность атаки, включая любые родственные домены. В частности, уязвимости, позволяющие инициировать произвольный вторичный запрос (например, XSS), могут полностью компрометировать защиты, основанные на «Site», подвергая все домены сайта Cross-Site атакам.

Помимо классического CSRF, не забывайте, что если целевой сайт поддерживает WebSockets, эта функциональность может быть уязвима к CSWSH, что по сути представляет собой CSRF-атаку, нацеленную на рукопожатие WebSocket. Подробности см. в разделе о тестировании WebSockets.

Cookie с ограничениями SameSite Lax обычно не отправляются в Cross-Site POST-запросах, но есть некоторые исключения.

Как уже упоминалось, если сайт не включает атрибут SameSite при установке cookie, Chrome автоматически применяет ограничения Lax по умолчанию. Однако, чтобы не ломать механизмы SSO (единый вход), он фактически не применяет эти ограничения для первых 120 секунд для POST-запросов навигации верхнего уровня. В результате возникает двухминутное окно, в течение которого пользователи могут быть уязвимы к Cross-Site атакам.

Note

Это двухминутное окно не относится к cookie, которые были явно установлены с атрибутом SameSite=Lax.

Пытаться подгадать атаку под это короткое окно на практике неудобно. С другой стороны, если вы найдёте на сайте гаджет, позволяющий принудительно выдать жертве новый сессионный cookie, вы можете заранее «обновить» её cookie, а потом выполнить основную атаку. Например, завершение входа на основе OAuth может каждый раз приводить к новой сессии, поскольку OAuth-сервис не обязательно знает, залогинен ли пользователь на целевом сайте.

Чтобы инициировать обновление cookie без необходимости ручного повторного входа жертвы, вам нужно использовать навигацию верхнего уровня, которая гарантирует включение cookie, связанных с текущей OAuth-сессией. Это создаёт дополнительную сложность, поскольку затем нужно вернуть пользователя на ваш сайт, чтобы вы могли выполнить финальную атаку.

В качестве альтернативы вы можете инициировать обновление cookie в новой вкладке, чтобы браузер не покинул страницу до доставки финальной атаки. Небольшая проблема этого подхода в том, что браузеры блокируют всплывающие вкладки (pop-up), если они не открыты через ручное взаимодействие. Например, следующий pop-up по умолчанию будет заблокирован браузером:

Чтобы обойти это, вы можете обернуть команду в обработчик события onclick следующим образом:

Таким образом, метод window.open() вызывается только тогда, когда пользователь кликает где-нибудь на странице.

Last updated