Разбираемся с атрибутом SameSite
cookies
Перевод статьи Rowan Merewood SameSite cookies explained
Защитите свой сайт, узнав как явно определить кросс-сайтовые cookies.
Cookies
являются одним из способов хранения состояния на веб-сайтах. Многие годы их возможности росли и развивались, при этом оставляя веб-платформу с некоторыми устаревшими проблемами. Чтобы это исправить, браузеры (включая Chrome, Firefox и Edge) изменили свое поведение в сторону защиты приватных данных пользователей.
Каждый cookie
представляет собой пару ключ-значение key = value
с определенным набором атрибутов, которые контролируют когда и где будет использоваться cookie
. Возможно, вы уже использовали эти атрибуты, чтобы например установить срок действия cookie
, или передачу только по HTTPS. Сервера устанавливают cookies
отправляя специальный заголовок Set-Cookie
в ответе. За всеми подробностями можно обратиться к спецификации RFC6265bis, но вот краткий обзор того, как это работает.
Допустим, у вас есть блог, где вы хотите показывать промо "Что нового" своим подписчикам. Пользователь может пропустить промо, и вряд ли после этого захочет увидеть его снова, по крайней мере, на некоторое время. Вы можете записать эту настройку в cookie
и установить его срок действия в 1 месяц (2,600,000 сек), а также отправлять его только по HTTPS. В этом случае заголовок будет выглядеть так:
Set-Cookie: promo_shown=1; Max-Age=2600000; Secure
Сервер устанавливает cookies, используя заголовок Set-Cookie
Когда ваш посетитель откроет страницу, удовлетворяющую этим требованиям, т.е. соединение защищено и cookie
не старше месяца, браузер отправит этот заголовок в своем запросе:
Cookie: promo_shown=1
Ваш браузер отправляет cookies обратно в заголовке Cookie
Вы также можете добавлять и смотреть cookies
доступные на сайте используя document.cookie
.
Присваивание значения document.cookie
создаст или перезапишет cookie
с этим ключом. Например, попробуйте выполнить следующее в JavaScript консоли своего браузера:
document.cookie = "promo_shown=1; Max-Age=2600000; Secure"
// "promo_shown=1; Max-Age=2600000; Secure"
Чтение document.cookie
выведет все cookies
доступные в текущем контексте, разделив их ;
document.cookie;
// "promo_shown=1; color_theme=peachpuff; sidebar_loc=left"
JavaScript получает доступ к cookies с помощью document.cookie
Если вы попробуете выполнить это на одном из популярных сайтов, то заметите, что большинство из них устанавливают значительно больше чем 3 cookies
. Чаще всего, эти cookies
отправляются в каждом запросе к домену, что приводит к ряду последствий. Обычно, для пользователей, ограничение исходящего трафика более строгое чем входящего. Так что, все излишние издержки на трафик исходящих запросов приводят к потерям в скорости. Будьте консервативными к числу и размеру cookies
, которые вы устанавливаете. Используйте атрибут Max-Age
, чтобы убедиться, что cookies
не хранятся дольше чем необходимо.
Какие cookies
являются основными а какие сторонними?
Если вернуться к тем популярным сайтам на которые мы смотрели выше, вы, возможно, заметили что cookies
представлены для разных доменов, а не только для того, на котором вы находитесь.
Cookies
домен которых совпадает с текущим доменом из адресной строки браузера являются основными.
Соответственно, Cookies
домен которых отличается от текущего являются сторонними.
Эти определения не являются абсолютными, они зависят от окружения пользователя: один и тот же cookie
может быть как основным так и сторонним в зависимости от сайта, на котором находится пользователь в текущий момент.
Cookies могут приходить с множества разных доменов на одной странице.
Продолжим с примером блога, допустим в одном из постов есть прикольная картинка с котэ и она хостится на /blog/img/amazing-cat.png
. Поскольку картинка очень крутая, другой пользователь захотел использовать ее у себя на сайте. Если, пользователь уже посещал ваш блог, и у него есть cookie
promo-shown
, то когда он захочет посмотреть на картинку с котэ на другом сайте cookie
будет прикреплен к запросу на получение картинки. Это определенно никому не нужно, т.к. promo_shown
не используется на другом сайте, это излишняя нагрузка на запрос
Если это незапланированный эффект, зачем вам это делать? Существует механизм, позволяющий сайтам хранить
состояние, когда они используются в стороннем контексте. Например, вы добавили видео с YouTube на своем сайте. В плеере, посетитель увидит кнопку "Посмотреть позже". Если ваш посетитель уже залогинен на YouTube, то его сессия доступна во встроенном плеере через сторонние cookies
. Это значит, что кнопка "Посмотреть позже" просто сохранит видео, вместо запроса на логин или редиректа на YouTube.
Сookie в стороннем контексте отправляются при посещении разных страниц.
Одна из традиционных особенностей веба в том, что веб, по умолчанию, привык быть открытым. Поэтому, так много людей могут создавать приложения и размещать контент. Однако, эта особенность приводит к появлению ряда проблем в безопасности и приватности данных пользователя. Атаки фальсификации межсайтовых запросов (CSRF - Cross-Site Request Forgery) основаны на том факте, что cookies
прикрепляются к каждому запросу, в независимости от того, кто является инициатором запроса. Например, если вы зашли на evil.example
, то он может сделать запрос на your-blog.example
и ваш браузер спокойненько прикрепит соответствующие cookies
. И если ваш блог не заботится о проверке запросов то evil.example
может спровоцировать действия типа удаления постов, или размещения собственного контента.
Пользователи также становятся более уязвимыми, от того как cookies
используются для отслеживания их активности по разным сайтам. Тем не менее, до сих пор, отсутствовал способ явно заявить о намерениях использования cookies
. Ваш cookie
promo_shown
должен отправляться только в основном контексте, учитывая при этом cookie
сессии для встраиваемых виджетов, которые позволяют хранить состояние авторизации других сайтов в стороннем контексте.
Объявляем об использовании cookies
с атрибутом SameSite
Включение атрибута SameSite
(определенного в RFC6265bis) позволяет вам определить: должен ли ваш cookie
использоваться только в основном или same-site
контексте. Полезно понимать что вообще здесь значит site
. Site
- сочетание суффикса домена и части домена перед ним. Например, www.web.dev
домен - часть сайта web.dev
Ключевое понятие:
Если пользователь находится на www.web.dev
и запрашивает изображение с static.web.dev
то это является same-site
запросом.
Список суффиксов доменов определен здесь, так что это не только домены высшего уровня типа .com
но также и сервисы например github.io
. Это позволяет считать your-project.github.io
и my-project.github.io
разными сайтами.
Ключевое понятие:
Если пользователь на your-project.github.io
и запрашивает изображение с my-project.github.io
то это
межсайтовый (cross-site) запрос.
Включение атрибута SameSite
в cookie
предоставляет три возможных варианта управления поведением. Вы можете не определять аттрибут, либо использовать Strict
или Lax
для ограничения передачи cookie
только на собственном сайте.
Если вы назначите SameSite
значение Strict
, ваш cookie
будет пересылаться только в основном контексте. Для пользователя, это означает что cookie
будет отправлен только сайту из адресной строки браузера. Так что, если promo_shown
cookie
будет определен следующим образом:
Set-Cookie: promo_shown=1; SameSite=Strict
Когда ваш пользователь находится у вас на сайте, cookie
будет отправлен в запросе, как и ожидается. С другой стороны, когда пользователь пройдет по ссылке на ваш сайт, например, с другого сайта или из email от своего друга, в инициализирующем запросе cookie
отправлен не будет. Это хорошо в том случае, если cookies
относятся к функционалу за пределами начальной навигации, такому как смена пароля или создание заказа. Но, это слишком строго для promo_shown
. Если ваш пользователь пройдет по ссылке на сайт, он бы хотел чтобы cookie
был отправлен и, соответственно его выбор не смотреть промо сработал корректно.
Здесь нам поможет SameSite=Lax
, разрешая отправлять cookie
на верхнем уровне навигации. Давайте посетим страницу с постом о котэ, перейдя на нее с другого сайта по ссылке на оригинальный пост. Другой сайт может использовать фото котэ с вашего сайта напрямую и предоставить ссылку на оригинальный пост.
<p>Look at this amazing cat!</p>
<img src="https://blog.example/blog/img/amazing-cat.png" />
<p>Read the <a href="https://blog.example/blog/cat.html">article</a>.</p>
А cookie
установлен следующим образом:
Set-Cookie: promo_shown=1; SameSite=Lax
Когда пользователь придет на другой сайт,cookie
не будет прикреплен к запросу на получение amazing-cat.png
. Однако при переходе по ссылке к оригинальному посту cat.html
в вашем блоге, запрос будет включать cookie
. Это делает Lax
хорошим выбором для cookies
влияющих на отображение, тогда как Strict
полезен для cookies
, которые относятся к действиям пользователя
Предупреждение:
Ни Strict
ни Lax
не являются полноценным решением для обеспечения безопасности сайта. Cookies
отправляются как часть запросов пользователя и с ними нужно обращаться также как и с другими пользовательскими данными. Нужно следить за чистотой и корректностью передаваемых данных. Никогда не используйте cookies
для хранения секретных данных со стороны сервера.
Наконец, есть вариант не определять значение, что подразумевает собой отправку cookies
во всех контекстах. В последнем черновике спецификации RFC6265bis это можно сделать явно определив SameSite=None
. Это означает что можно использовать None
если вы намеренно хотите отправлять cookies
сторонним сайтам.
Определяйте контекст использования cookie явно, через: None, Lax, или Strict.
Если вы предоставляете сервис, которые другие сайты добавляют как виджет, встраиваемое содержимое, рекламу, или вам необходимо поддержка авторизации на разных сайтах, необходимо использовать None
, чтобы ваши намерения были ясны.
Изменения в стандартном поведении без SameSite
Несмотря на то, что аттрибут SameSite
широко поддерживается, он, к сожалению, мало используется разработчиками. По умолчанию, такое открытое поведение при отправке cookies
, позволит во всех случаях их использовать, но при этом сделает пользователя уязвимым для CSRF атак и непредвиденных утечек личной информации. Чтобы обязать разработчиков явно заявлять о намерениях при использовании cookies
и предоставлять пользователям более безопасные приложения, Инженерный Совет Интернета (IETF - Internet Engineering Task Force) предложил улучшение работы с cookies
с двумя изменениями:
Cookies
без атрибутаSameSite
трактуются как:SameSite=Lax
.Cookies
с атрибутомSameSite=None
должны также содержать аттрибутSecure
, и передаваться только по защищенному протоколу.
Chrome добавил эти изменения начиная с версии 84. В Firefox они тестируются с версии 69 и в дальнейшем будут активны по умолчанию. Чтобы протестировать эти изменения в Firefox, откройте about:config
и включите опцию network.cookie.sameSite.laxByDefault
. Edge также планирует изменить свое стандартное поведение.
SameSite=Lax
по умолчанию
Атрибут не выбран:
Set-Cookie: promo_shown=1
Если вы отправите cookie
без аттрибута SameSite
…
Применится стандартное поведение:
Set-Cookie: promo_shown=1; SameSite=Lax
Браузер будет работать с cookie
как при SameSite=Lax
.
Даже несмотря на то что теперь предоставляется более безопасное поведение по умолчанию, вам лучше явно определить SameSite
, чем предоставить это решение браузеру. Это сделает ваши намерения в отношении cookie
прозрачными и улучшит кросс-браузерную поддержку.
Внимание:
Стандартное поведение Chrome чуть менее строгое чем явный SameSite=Lax
, поскольку разрешена отправка некоторых cookies
для POST запросов верхнего уровня. Подробнее об этом можно узнать здесь. Это временное снижение ограничений, если вам нужно передавать cookies
сторонним сайтам, необходимо использовать: SameSite=None; Secure
.
SameSite=None
должны быть безопасными
Отклонено:
Set-Cookie: widget_session=abc123; SameSite=None
Назначение cookie
без Secure
будет отклонено.
Принято:
Set-Cookie: widget_session=abc123; SameSite=None; Secure
Вы должны быть уверены что аттрибут SameSite=None
используется в паре с аттрибутом Secure
.
Вы можете протестировать это поведение с версии Chrome 76 включив chrome://flags/#cookies-without-same-site-must-be-secure
и в Firefox 69 в about:config
, установив network.cookie.sameSite.noneRequiresSecure
.
Вам нужно их применить, когда вы устанавливаете новые cookies
и активно обновляете существующие cookies
даже если не истек их срок действия.
Если вы зависите от сервисов, которые предоставляют сторонний контент на вашем сайте, вы также должны следить за тем что они обновляют свое поведение. Вам необходимо обновлять зависимости,чтобы быть уверенным, что ваш сайт поддерживает новое поведение.
Оба этих изменения обратно-совместимы с браузерами, которые корректно воспроизводят предыдущую версию аттрибута SameSite
или вообще ее не поддерживают. Аналогично, любые клиенты не поддерживающие SameSite=None
до сих пор, должны игнорировать его и обрабатывать таким образом, как будто этот аттрибут не задан.
Предупреждение:
Определенное число старых версий браузеров Chrome, Safari, и UC несовместимы с новым значением None
и могут игнорировать или отклонять cookie
. Это поведение исправлено в текущих версиях, но необходимо проверить соотношение пользователей использующих старые версии. Список неподдерживаемых клиентов можно найти на сайте Chromium.
Огромное спасибо за участие и обратную связь Lily Chen, Malte Ubl, Mike West, Rob Dodson, Tom Steiner, и Vivek Sekhar