Спаситель от CSRF - Same-site cookie
Когда речь идет о защите от CSRF
, независимо от используемого метода, как фронтенд, так и на бэкенд должны реализовать комплексный механизм защиты от него. Ранее, обсуждая XSS
, мы упоминали CSP
, который может блокировать ресурсы, не соответствующие правилам. Но предоставляет ли браузер аналогичный способ предотвращения CSRF
? Есть ли что-то, что мы можем добавить, чтобы предотвратить CSRF
?
Да, существует такое понятие, как cookie с атрибутом Same-Site
.
Изучение Same-Site Cookie
Как следует из названия, Cookie
с атрибутом Same-Site
— это Cookie
, которые отправляются только при соблюдении условий Same-Site
. Он используется путем установки атрибута под названием SameSite
, который может иметь три значения:
None
Lax
Strict
None
это самое мягкое значение, означающее "я не хочу атрибут SameSite".
С другой стороны, Strict
— это самое строгое значение. Когда вы добавляете его, это явно указывает, что "этот cookie может быть отправлен только при условии same-site".
Например, предположим, что cookie установлен с атрибутом SameSite=Strict на https://api.example.com
. В этом случае запросы, отправленные с https://randomsite.com
на https://api.example.com
не будут включать этот cookie, потому что эти два веб-сайта не являются same-site.
Однако, если это https://test.example.com
, cookie будет включен, потому что это same-site.
Насколько это строго? Это настолько строго, что даже "клик по ссылке считается". Если я нажимаю на гиперссылку <a href="https://api.example.com"></a>
на https://randomsite.com
, это эквивалентно отправке Cross-Site
запроса с https://randomsite.com
to https://api.example.com
.
Поэтому в этом случае cookie не будет включен.
Но разве это не неудобно? Давайте возьмем Google в качестве примера. Предположим, Google использует cookie с атрибутом same-site для проверки идентичности пользователя, и в моей статье есть гиперссылка, которая ведет на страницу поиска Google. Когда пользователь нажимает на ссылку, открытая страница Google будет в состоянии выхода из системы, потому что у нее нет токена. Это приводит к плохому пользовательскому опыту.
Существует два решения этой проблемы. Первое похоже на подход Amazon, который включает подготовку двух наборов cookie. Первый набор поддерживает статус входа в систему, в то время как второй набор используется для чувствительных операций (таких как покупка товаров или настройки учетной записи). Первый набор не имеет атрибута SameSite, поэтому он будет поддерживать статус входа в систему независимо от того, откуда поступает запрос. Однако даже если злоумышленник имеет первый набор cookie, он не сможет ничего сделать, потому что не сможет выполнять никакие операции. Второй набор, с другой стороны, полностью избегает CSRF
, устанавливая атрибут SameSite
.
Однако этот подход может быть немного громоздким. Поэтому вы можете рассмотреть второе решение, которое заключается в переходе на другой режим SameSite: Lax
.
Режим Lax ослабляет некоторые ограничения. В основном, если это "навигация верхнего уровня", такая как <a href>
или <form method="GET">
, cookie все равно будут включены. Однако, если это форма с методом POST, cookie не будет включен.
Таким образом, вы можете сохранить гибкость, позволяя пользователям поддерживать статус входа в систему, когда они приходят с других веб-сайтов, одновременно предотвращая атаки CSRF
.
Если Cross-Site
запросы не включают cookie, злоумышленники не могут осуществить атаки CSRF
.
История Same-site cookie
Однако после того, как пандемия немного утихла в июле, это изменение было постепенно повторно развернуто и полностью внедрено к августу.
Перерыв на размышления
К этому моменту все должны быть в какой-то степени знакомы с принципами и методами защиты от CSRF
. Cookie с атрибутом Same-Site
, представленный в этой статье, кажется довольно надежным, и браузеры даже автоматически делают его значением по умолчанию, позволяя вам наслаждаться преимуществами без каких-либо настроек.
С настройкой по умолчанию SameSite=Lax
, CSRF
, похоже, покинул сцену, официально объявленный мертвым, став слезой времени. Не обязательно добавлять CSRF-токен, потому что cookie с атрибутом Same-Site
автоматически все обработает.
Однако действительно ли это так?
Действительно ли значение по умолчанию SameSite=Lax
настолько мощно? Нужно ли нам все еще добавлять CSRF-токен вместе с ним? Будут ли какие-либо проблемы, если мы его не добавим? Какие ситуации могут вызвать проблемы?
Сначала подумайте над этими вопросами, а затем продолжайте читать.
CSRF с помощью GET-запросов
В предыдущих примерах, когда я вводил CSRF
, я всегда использовал POST-запросы. Причина проста: CSRF
сосредоточен на выполнении действий, и, как правило, GET-запросы не используются для выполнения действий, поскольку это не соответствует семантике метода GET (или, более профессионально, GET подходит только для идемпотентных операций).
Однако "не подходит" не означает "невозможно сделать".
Как я упоминал в первом примере, говоря о CSRF
, некоторые люди могут идти на упрощения и использовать GET для реализации удаления или других функций, например: /delete?id=3
.
В этом случае SameSite lax
не может обеспечить защиту, потому что lax
позволяет следующее поведение:
Перенаправление на такие страницы является одним из разрешенных поведений. Поэтому даже с куки по умолчанию Same-Site
защита все равно не может быть обеспечена.
В будущем, когда вы увидите, что кто-то пишет о "выполнении действий с помощью GET", помимо того, что вы скажете им, что это плохая практика, у вас теперь есть еще одна причина: "Делая это, вы создаете проблемы с безопасностью".
Однако таких людей, которые пишут это таким образом, должно быть немного, верно? Так что проблема не должна быть значительной?
Для такого рода написания это действительно редко, но есть еще один распространенный механизм, который мы можем использовать: переопределение метода.
Атрибут method
в HTML-формах представляет HTTP-метод, используемый при отправке запроса. Он поддерживает только два значения: GET и POST.
Что если мы хотим использовать PUT, PATCH или DELETE? Это невозможно. Либо нам нужно использовать fetch()
для отправки запроса, либо реализовать обходной путь на сервере, что поддерживают многие фреймворки.
Для некоторых веб-фреймворков, если запрос имеет заголовок X-HTTP-Method-Override
или строка запроса содержит параметр _method
, значение внутри будет использоваться как метод запроса вместо оригинального HTTP-метода.
Это изначально использовалось в сценарии, который я только что упомянул, когда вы хотите обновить данные, но можете использовать только POST
. Вы можете добавить параметр _method
, чтобы сообщить серверу, что это на самом деле запрос PATCH
:
Но это также может быть использовано в нашей атаке CSRF
. Например, GET /api/deleteMyAccount?_method=POST
будет рассматриваться сервером как POST-запрос, а не GET.
Скрытые правила использования Same-site Cookies
Итак, если нет поддержки переопределения метода и нет неуместных операций с использованием GET, значит ли это, что все в порядке? Конечно, не так просто.
Куки Same-Site
по умолчанию на самом деле имеют скрытое правило, или скорее, менее известное правило, которое было упомянуто Firefox:
For any flows involving POST requests, you should test with and without a long delay. This is because both Firefox and Chrome implement a two-minute threshold that permits newly created cookies without the SameSite attribute to be sent on top-level, cross-site POST requests (a common login flow).
Это означает, что для куки без атрибута SameSite
они могут обойти некоторые из ограничений lax
в течение первых двух минут после записи, позволяя "верхнеуровневые кросс-сайтовые POST-запросы", другими словами, <form method=POST>
.
Поэтому давайте предположим, что пользователь только что вошел на сайт, и куки, используемые для аутентификации, только что были записаны. В это время пользователь открывает веб-страницу, созданную злоумышленником, и содержимое веб-страницы является эксплойтом CSRF:
Из-за упомянутого ранее исключения атака CSRF
будет успешной.
Это исключение изначально было добавлено, чтобы предотвратить сбои на определенных веб-сайтах, но в то же время оно открыло лазейку для злоумышленников. При выполнении определенных условий ограничения "по умолчанию lax" могут быть проигнорированы.
Если веб-сайт явно указывает SameSite=Lax
, то этой проблемы не будет. Так значит ли это, что он действительно безопасен?
Я думаю, вы знаете, что я собираюсь сказать.
Достаточно ли куки Same-site для предотвращения CSRF?
Хотя CSRF
означает Cross-Site
, чаще всего это больше похоже на Cross-Origin
. Другими словами, если злоумышленник может запустить атаку с assets.example.com
на example.com
, мы обычно будем считать это CSRF
, даже если эти два веб-сайта не являются Cross-Site
.
Куки Same-Site
только гарантируют, что куки не будут отправлены в Cross-Site
сценариях. Но если два веб-сайта являются Same-Site
, это не имеет значения.
Продолжая с предыдущего примера, предположим, что основной веб-сайт Facebook — это www.facebook.com, и у него есть тестовая среда под названием sandbox.facebook.com, где была обнаружена уязвимость XSS
.
Если веб-сайт полагается только на куки Same-Site
для предотвращения CSRF
, то в этом сценарии это совершенно бесполезно, потому что www.facebook.com и sandbox.facebook.com явно являются Same-Site
. Поэтому мы можем легко запустить атаку CSRF
на основной веб-сайт, используя уязвимость XSS
, найденную в песочнице.
Но это явно уязвимость, против которой следует защищаться, потому что мы не хотим, чтобы поддомены могли атаковать другие домены.
Developers are strongly encouraged to deploy the usual server-side defenses (CSRF tokens, ensuring that "safe" HTTP methods are idempotent, etc) to mitigate the risk more fully.
Настоятельно рекомендуется, чтобы разработчики реализовывали обычные меры защиты, такие как токены CSRF, в дополнение к куки Same-Site
.
Таким образом, даже при наличии куки Same-Site
это не означает, что предыдущие меры защиты можно удалить. Нам все еще нужны токены CSRF, в сочетании с куки Same-Site
, чтобы построить более надежную защитную стену и предотвратить различные сценарии атак.
Пример из реальной жизни
Однако есть причина, по которой Grafana так считает. API Grafana принимает только запросы с типом контента application/json
, и этот тип контента нельзя отправить через форму. Можно использовать только fetch
, и этот тип контента попадает под категорию непростых запросов, поэтому требуется предварительный запрос (preflight)
.
Поскольку есть предварительный запрос, запросы с других источников должны быть заблокированы, так что теоретически проблем быть не должно.
Но, внимательно прочитав спецификацию CORS
и обнаружив небольшую ошибку на сервере, это ограничение было успешно обойдено.
MIME-тип
состоит из трех частей: типа, подтипа и параметров. Мы часто видим application/json
, где тип — application
, подтип — jso
n, и нет параметров.
Однако text/plain; charset=utf-8
имеет тип text
, подтип plain
и параметр charset=utf-8
.
Спецификация CORS
требует, чтобы тип и подтип были одним из следующих:
application/x-www-form-urlencoded
multipart/form-data
text/plain
Но она не ограничивает содержание параметров.
Таким образом, этот тип контента может быть простым запросом: text/plain; application/json
. application/json
является параметром, а text/plain
— это тип + подтип, что полностью соответствует спецификации.
Логика обработки на стороне API выглядит следующим образом:
Здесь используется strings.contains
для всего типа контента, поэтому, хотя передаваемый нами тип контента по сути text/plain
, сервер обрабатывает его как допустимый JSON
из-за параметров.
После обхода этого ограничения мы можем использовать fetch
для инициации атаки CSRF
с сайта того же происхождения.
Предположим, что Grafana размещена по адресу https://grafana.example.com
, нам нужно найти хотя бы одну уязвимость XSS
или получить контроль над всем доменом *.example.com
, чтобы запустить атаку. Хотя это может быть сложно, это не невозможно.
Как я уже упоминал ранее, это атака, инициированная с того же сайта, поэтому куки Same-Site
не могут предотвратить ее. Строго говоря, если рассматривать буквальное значение, это нельзя назвать CSRF
, потому что это не кросс-сайт. Однако давать этому новое название кажется странным.
Заключение
В этой статье мы представили новые меры, которые недавно внедрили основные браузеры, устанавливая куки по умолчанию на SameSite=Lax
. Хотя это действительно увеличивает некоторую безопасность, не думайте, что использование этого одного лишь метода может полностью предотвратить CSRF
.
Так же как защита от XSS
, защита от CSRF
также требует многослойной защиты, чтобы гарантировать, что если одна линия обороны будет нарушена, будут другие защиты, которые смогут помочь. Например, если используются только куки Same-Site
, это означает капитуляцию, когда другой сайт того же происхождения будет скомпрометирован. Вместо этого лучше реализовать дополнительные меры защиты, такие как токены CSRF, которые могут по крайней мере смягчить последствия, когда сайт того же происхождения будет скомпрометирован.
Говоря об этом, легко ли получить контроль над другими сайтами того же происхождения? И что можно сделать, получив контроль? Каждый может подумать над этими вопросами, и мы обсудим их в следующей статье.
Дополнительные ссылки:
Last updated