CSRF – Spring Security в деталях

Наверняка каждый разработчик веб-приложений или сайтов на практике сталкивался с защитой от CSRF-атак, предоставляемой фреймворками и библиотеками. В фреймворке Spring Security защита от этого вида эксплойтов тоже присутствует, однако многие начинающие разработчики предпочитают отключать её, не пытаясь разобраться в природе CSRF-атак, способах защиты от них и правильном использовании средств защиты.

В этой статье будет описан принцип действия CSRF-атаки и продемонстрированы несколько вариантов приведения таких атак. Затем будут перечислены признаки уязвимости веб-приложений перед CSRF-атаками и способы защиты от них. После этого будет подробно описана защита от CSRF-атак, предоставляемая Spring Security: компоненты, реализующие её, и их настройка. В завершение статьи будет описано несколько сценариев использования защиты от CSRF-атак в разных видах веб-приложений: в сайтах со статическими страницами, в сайтах с асинхронными запросами и в одностраничных веб-приложениях (SPA, PWA).

Принцип действия CSRF-атаки

Для сохранения данных между запросами и сессиями на стороне браузера в протоколе HTTP предусмотрены куки. Куки часто используются для хранения сессионных данных пользователей, так, например, Apache Tomcat устанавливает куки JSESSIONID, по которым он может определить HTTP-сессию пользователя. Установленные сайтом куки передаются в заголовках любых запросов к сайту, будь то запрос на получение изображения или файла каскадных таблиц стилей или будь то запрос какой-нибудь HTML-страницы.

Таким образом, если на странице одного сайта размещено, допустим, изображение со второго сайта, то при открытии этой страницы браузер отправит запрос на получение изображения со второго сайта, в заголовках которого среди прочих будут и заголовки Cookie для второго сайта. Да, первый сайт и его клиентский код не получат доступ к этим куки, но этого и не требуется для проведения атаки.

Злоумышленник может воспользоваться таким поведением для проведения CSRF-атак на уязвимый сайт, используя свой сайт для отправки запросов от имени аутентифицированного на атакуемом сайте пользователя. Для этого злоумышленник может разместить на своём сайте код, выполняющий нежелательные запросы к атакуемому сайту — все они будут содержать куки пользователя, зашедшего на сайт злоумышленника.

Схематическое изображение CSRF-атаки

Признаки уязвимости к CSRF-атакам

Сайт считается уязвимым к CSRF-атакам, в первую очередь, если для его работы используются сессионные куки и не используется защита от CSRF-атак, что очевидно. Но предлагаю разобраться с каждым фактором более подробно.

Наличие сессионных данных в файлах куки

Наличие сессионных данных в файлах куки является главным фактором для проведения CSRF-атаки — если на сайте не используются файлы куки для поддержания HTTP-сессии, то CSRF-атаки становятся неосуществимы, и защита от них не требуется. Впрочем, избегать использование файлов куки в качестве хранения сессионных данных не нужно, гораздо проще использовать защиту от CSRF-атак.

Использование неподходящих HTTP-методов

Все ресурсы браузеры запрашивают при помощи метода GET, будь то HTML-страница, файл каскадных таблиц стилей, изображение или шрифт. Метод GET относится к так называемым «безопасным» методам, среди которых кроме него есть HEADOPTIONS и TRACE, эти методы должны использоваться исключительно для получения информации, но не для совершения каких-либо действий на сайтах. В противном случае это может в какой-то степени упростить задачу злоумышленнику — вместо размещения формы с кнопкой для отправки нежелательного запроса, он может использовать имитацию встраивания элементов HTML-страницы:

Всё коварство такой имитации встраивания элементов заключается в том, что браузеры выполняют GET-запросы для получения соответствующих ресурсов автоматически без участия пользователей.

Неправильные настройки CORS

Настройки CORS, не ограничивающие список отправляющих запросы сайтов, методы, заголовки и аутентификационные данные делают осуществимыми CSRF-атаки при помощи клиентского кода JavaScript, размещённого на сайте злоумышленника и исполняемого браузером. Более того, такие настройки позволяют злоумышленнику написать код, позволяющий проверить наличие пользовательской HTTP-сессии на атакуемом сайте и скорректировать поведение вредоносного сайта и даже атакуемого пользователя.

Варианты проведения CSRF-атак

Допустим на атакуемом сайте есть адрес для смены пароля. Пользователь атакуемого сайта, будучи аутентифицированным на нём, в процессе интернет-сёрфинга натыкается на сайт злоумышленника и на какой-либо его странице нажимает на кнопку, предлагающую выиграть миллион рублей, долларов или рупий. А на деле со страницы сайта злоумышленника отправляется запрос на смену пароля на атакуемом сайте. Так в общих чертах выглядит типичная CSRF-атака.

Отправка межсайтового запроса при помощи формы

Самый простой способ реализации CSRF-атаки — при помощи формы:

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

Имитация встраивания в HTML-страницу

Если на атакуемом сайте используется GET-метод для выполнения действий, то задача злоумышленника упрощается — теперь он может использовать имитацию встраивания изображений, файлов каскадных таблиц стилей или JavaScript для совершения запросов к атакуемому сайту:

Скорее всего, пользователь даже не поймёт, что произошло что-то не то.

Отправка асинхронного межсайтового запроса

Использование неограниченных или слабых настроек CORS могут привести к тому, что злоумышленник может разместить на своем сайте клиентский код, который отправит нежелательный запрос к атакуемому сайту без ведома пользователя, даже если используются правильные HTTP-методы:

Способы защиты от CSRF-атак

Защитить своё веб-приложение можно при помощи CSRF-токенов, отправляемых вместе с запросами, использованием правильных HTTP-методов и строгими настройками CORS.

Использование CSRF-токенов

Для защиты от CSRF-атак при выполнении любого действия в веб-приложении должны использоваться CSRF-токены. Серверная часть приложения должна сгенерировать токен, который будет ассоциироваться с HTTP-сессией пользователя, и передать его клиентской части. Клиентская часть при выполнении действия должна передавать этот токен в запросе: в строке, теле или заголовке запроса (но не в куки, тогда весь смысл CSRF-токена теряется). Серверная часть при выполнении действия должна провалидировать полученный CSRF-токен.

CSRF-токен позволяет решить проблему CSRF-атак благодаря тому, что он, в отличие от куки, не передаётся в запросе автоматически, клиентский код должен явно его вкладывать в запрос, что невозможно сделать на сайте злоумышленника.

Обычно CSRF-токен создаётся для HTTP-сессии и срок его действия соответствует сроку жизни HTTP-сессии, однако для повышения уровня защищённости можно периодически создавать новый CSRF-токен или и вовсе создавать новый CSRF-токен для каждого запроса.

Кроме этого во избежание уязвимостей BREACH и CRIME желательно реализовать маскировку или шифрование CSRF-токена при передаче его от клиента к серверу таким образом, чтобы зашифрованный CSRF-токен был уникален. Про это я рассказывал в статье «Spring Security: Маскировка CSRF-токена».

Использование подходящих HTTP-методов

Для выполнения действий в веб-приложениях должны использоваться соответствующие методы: в классических веб-приложениях — это POST, а в REST API ещё и PUTPATCH и DELETE.

По умолчанию настройки защиты от CSRF-атак в Spring Security учитывают то, что методы GETHEADOPTIONS и TRACE являются безопасными, и запросы, использующие данные методы, не защищаются от CSRF-атак, следовательно, использовать их для совершения действий небезопасно.

CORS

Для повышения уровня защиты веб-приложения от CSRF-атак с использованием клиентского кода важно использовать строгие настройки CORS, допускающие межсайтовые запросы только от доверенных сайтов.

Spring Security

Защита от CSRF-атак является частью библиотеки spring-security-web в Spring Security, так как специфична для веб-приложений. Основные компоненты, реализующие защиту от CSRF-атак: CsrfFilterCsrfTokenRequestHandlerCsrfTokenRepository и CsrfToken.

CsrfFilter

Фильтр CsrfFilter — точка входа для защиты от CSRF-атак. Данный фильтр определяет, нужно ли применять защиту от CSRF-атак к поступившему запросу, и если да, то при помощи CsrfTokenRepository он получает связанный с текущей HTTP-сессией CSRF-токен (ожидаемый) и валидирует полученный в запросе CSRF-токен при помощи CsrfTokenRequestHandler.

Если в CsrfTokenRepository отсутствует ожидаемый CSRF-токен, то он будет создан и сохранён для дальнейших запросов.

Если к запросу не должна применяться защита от CSRF-атак, то этот фильтр просто передаст поток исполнения следующему фильтру в цепочке фильтров безопасности.

CsrfTokenRepository

Репозиторий CSRF-токенов CsrfTokenRepository нужен для хранения токенов на серверной стороне приложения. Spring Security предоставляет две основные реализации: HttpSessionCsrfTokenRepository — для хранения в HTTP-сессии и CookieCsrfTokenRepository — для хранения CSRF-токена в куки-файлах браузера. По умолчанию используется HttpSessionCsrfTokenRepository, в то время как CookieCsrfTokenRepository может использоваться в случае отказа от использования HTTP-сессий. При необходимости вы можете реализовать собственный способ хранения CSRF-токенов.

Стоит отметить тот факт, что CookieCsrfTokenRepository и HttpSessionCsrfTokenRepository по умолчанию используют разные заголовки для передачи CSRF-токенов. Так CookieCsrfTokenRepository использует заголовок X-XSRF-TOKEN, а HttpSessionCsrfTokenRepository — X-CSRF-TOKEN, впрочем, вы можете самостоятельно указать заголовок, в котором должен передаваться CSRF-токен, и он может отличаться от указанных двух.

CsrfTokenRequestHandler

Обработчик запроса CsrfTokenRequestHandler должен сравнить полученный из репозитория CSRF-токен с токеном, полученным из запроса. Используемый по умолчанию в Spring Security XorCsrfTokenRequestAttributeHandler ожидает, что в запросе токен должен быть замаскирован случайным набором байтов при помощи исключающей дизъюнкции (XOR). Так же доступен CsrfTokenRequestAttributeHandler, работающий с незамаскированными CSRF-токенами.

CsrfToken и DeferredCsrfToken

CSRF-токен в Spring Security описывается интерфейсом CsrfToken, который декларирует три метода:

  • getToken() возвращает сам токен
  • getHeaderName() возвращает название HTTP-заголовка в котором CsrfTokenRequestHandler ожидает получить переданный от клиента CSRF-токен
  • getParameterName() возвращает название параметра запроса в котором CsrfTokenRequestHandler ожидает получить переданный от клиента CSRF-токен

DeferredCsrfToken реализует отложенную загрузку токена.

Настройка цепочки фильтров безопасности

Защита от CSRF-атак может быть настроена в цепочке фильтров безопасности при помощи метода HttpSecurity.csrf():

Для настройки защиты от CSRF-атак доступны следующие методы:

  • Метод requireCsrfProtectionMatcher() определяет параметры запросов, которые должны обрабатываться фильтром CsrfFilter
  • При помощи метода ignoringRequestMatchers() можно определить параметры запросов, которые должны быть исключены из обработки фильтром CsrfFilter
  • Методом csrfTokenRepository() можно указать используемый репозиторий CSRF-токенов
  • Методом csrfTokenRequestHandler можно указать используемый обработчик CSRF-токенов
  • Методом sessionAuthenticationStrategy можно задать действие, выполняемое после успешной аутентификации, по умолчанию это смена CSRF-токена.

В целом можно использовать настройки защиты от CSRF-атак:

В настройках по умолчанию нет только игнорируемых запросов, остальные настройки соответствует описанным выше.

Сценарии использования защиты

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

Сайты со статическими страницами

В классических веб-приложениях со статическим содержимым HTML-страниц CSRF-токены можно вкладывать в запросы при помощи скрытого поля в форме. Движки шаблонов вроде Thymeleaf, Mustache или JSP могут получить доступ к параметрам CSRF-токена через свойство модели _csrf, ниже приведёт пример для Thymeleaf:

Сайты с асинхронными запросами

Если веб-приложение предполагает использование асинхронных запросов для отправки данных серверной сторонне, то в этом случае параметры CSRF-токена можно вывести в каких-нибудь элементах HTML-страницы, например, в тегах <meta>:

При отправке асинхронных запросов можно вкладывать CSRF-токен в заголовок:

Либо в тело запроса:

Одностраничные сайты (SPA, PWA)

В случае с одностраничными веб-приложениями возникает проблема — такие приложения могут существовать отдельно от серверной части, и возможность вывести параметры CSRF-токена в исходной HTML-странице может отсутствовать. В этом случае клиентское веб-приложение должно каким-то образом получить CSRF-токен от серверной стороны.

Разработчики Spring Security предлагают вариант, при котором CSRF-токены хранятся в файлах куки браузера, но при этом они доступны клиентской стороне, что достигается отсутствием флага httpOnly у куки, хранящей CSRF-токен. Для этого нужно сконфигурировать защиту от CSRF-атак следующим образом:

Клиентское веб-приложение должно получать CSRF-токен из куки перед отправкой запроса:

Альтернативный вариант заключается в создании эндпоинта, который бы предоставлял пользователю текущий CSRF-токен. Это можно реализовать при помощи фильтра:

Фильтр необходимо будет зарегистрировать в цепочке фильтров безопасности:

Альтернативно это можно сделать при помощи REST-контроллера или HandlerFunction. Теперь CSRF-токен можно получать при обращении к пути /csrf, результат будет выглядеть следующим образом:

На стороне клиентского веб-приложения теперь можно получать CSRF-токен перед отправкой запросов:

Полезные ссылки