Dmitry Vdovichenko

Разбираемся с атрибутом SameSite cookies

cookie-hero

Перевод статьи 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
set-cookie-response-header

Сервер устанавливает cookies, используя заголовок Set-Cookie

Когда ваш посетитель откроет страницу, удовлетворяющую этим требованиям, т.е. соединение защищено и cookie не старше месяца, браузер отправит этот заголовок в своем запросе:

Cookie: promo_shown=1
cookie-request-header

Ваш браузер отправляет 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"
document-cookie

JavaScript получает доступ к cookies с помощью document.cookie

Если вы попробуете выполнить это на одном из популярных сайтов, то заметите, что большинство из них устанавливают значительно больше чем 3 cookies. Чаще всего, эти cookies отправляются в каждом запросе к домену, что приводит к ряду последствий. Обычно, для пользователей, ограничение исходящего трафика более строгое чем входящего. Так что, все излишние издержки на трафик исходящих запросов приводят к потерям в скорости. Будьте консервативными к числу и размеру cookies, которые вы устанавливаете. Используйте атрибут Max-Age, чтобы убедиться, что cookies не хранятся дольше чем необходимо.

Какие cookies являются основными а какие сторонними?

Если вернуться к тем популярным сайтам на которые мы смотрели выше, вы, возможно, заметили что cookies представлены для разных доменов, а не только для того, на котором вы находитесь.

Cookies домен которых совпадает с текущим доменом из адресной строки браузера являются основными.

Соответственно, Cookies домен которых отличается от текущего являются сторонними.

Эти определения не являются абсолютными, они зависят от окружения пользователя: один и тот же cookie может быть как основным так и сторонним в зависимости от сайта, на котором находится пользователь в текущий момент.

document-cookie

Cookies могут приходить с множества разных доменов на одной странице.

Продолжим с примером блога, допустим в одном из постов есть прикольная картинка с котэ и она хостится на /blog/img/amazing-cat.png. Поскольку картинка очень крутая, другой пользователь захотел использовать ее у себя на сайте. Если, пользователь уже посещал ваш блог, и у него есть cookie promo-shown, то когда он захочет посмотреть на картинку с котэ на другом сайте cookie будет прикреплен к запросу на получение картинки. Это определенно никому не нужно, т.к. promo_shown не используется на другом сайте, это излишняя нагрузка на запрос

Если это незапланированный эффект, зачем вам это делать? Существует механизм, позволяющий сайтам хранить состояние, когда они используются в стороннем контексте. Например, вы добавили видео с YouTube на своем сайте. В плеере, посетитель увидит кнопку "Посмотреть позже". Если ваш посетитель уже залогинен на YouTube, то его сессия доступна во встроенном плеере через сторонние cookies. Это значит, что кнопка "Посмотреть позже" просто сохранит видео, вместо запроса на логин или редиректа на YouTube.

cross-site-cookie-request-header

С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 сторонним сайтам.

samesite-none-lax-strict

Определяйте контекст использования 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