Перейти к основному содержимому

CSRF с REST API без сохранения состояния

· 6 мин. чтения

1. Обзор

В нашей предыдущей статье мы объяснили, как атаки CSRF влияют на приложение Spring MVC.

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

2. Требуется ли REST API защита CSRF?

Во-первых, мы можем найти пример атаки CSRF в нашем специальном руководстве .

Теперь, прочитав это руководство, мы можем подумать, что REST API без сохранения состояния не будет затронут этим типом атаки, поскольку на стороне сервера нет сеанса для кражи.

Возьмем типичный пример: приложение Spring REST API и клиент Javascript. Клиент использует безопасный токен в качестве учетных данных (например, JSESSIONID или JWT ), который REST API выдает после успешного входа пользователя.

Уязвимость CSRF зависит от того, как клиент хранит и отправляет эти учетные данные в API .

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

Возьмем типичный пример: приложение Spring REST API и клиент Javascript. Клиент использует безопасный токен в качестве учетных данных (например, JSESSIONID или JWT ), который REST API выдает после успешного входа пользователя.

2.1. Учетные данные не сохраняются

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

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

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

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

Это настолько ограничивает пользователя, что эта опция используется редко .

2.2. Учетные данные, хранящиеся в хранилище браузера

Мы можем сохранить наш токен в хранилище браузера — например, в хранилище сеанса. Затем наш JavaScript-клиент может прочитать из него токен и отправить заголовок авторизации с этим токеном во всех REST-запросах.

Это распространенный способ использования, например, JWT: он прост в реализации и не позволяет злоумышленникам использовать CSRF-атаки . Действительно, в отличие от файлов cookie, переменные хранилища браузера не отправляются автоматически на сервер.

Однако эта реализация уязвима для XSS-атак : вредоносный код JavaScript может получить доступ к хранилищу браузера и отправить токен вместе с запросом. В этом случае мы должны защитить наше приложение .

Другой вариант — использовать файл cookie для сохранения учетных данных. Затем уязвимость нашего приложения зависит от того, как наше приложение использует файл cookie.

Мы можем использовать cookie только для сохранения учетных данных, как JWT, но не для аутентификации пользователя.

Наш клиент JavaScript должен будет прочитать токен и отправить его в API в заголовке авторизации.

В этом случае наше приложение не уязвимо для CSRF : даже если файл cookie автоматически отправляется через вредоносный запрос, наш REST API будет считывать учетные данные из заголовка авторизации, а не из файла cookie. Однако флаг HTTP-only должен быть установлен в false , чтобы наш клиент мог прочитать файл cookie.

Однако при этом наше приложение будет уязвимо для XSS-атак, как в предыдущем разделе.

Альтернативный подход заключается в аутентификации запросов из файла cookie сеанса с установленным в true флагом HTTP-only . Обычно это то, что Spring Security предоставляет с файлом cookie JSESSIONID. Конечно, чтобы наш API оставался без состояния, мы никогда не должны использовать сеанс на стороне сервера. ``

В этом случае наше приложение уязвимо для CSRF, как и приложение с отслеживанием состояния : поскольку файл cookie будет автоматически отправляться с любыми запросами REST, щелчок по вредоносной ссылке может выполнять операции с проверкой подлинности.

2.4. Другие уязвимые конфигурации CSRF

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

Это относится к базовой аутентификации HTTP , дайджест-аутентификации HTTP и mTLS .

Они не очень распространены, но имеют один и тот же недостаток: браузер автоматически отправляет учетные данные при любых HTTP-запросах. В этих случаях мы должны включить защиту CSRF.

3. Отключить защиту CSRF в Spring Boot

Spring Security включает защиту CSRF по умолчанию, начиная с версии 4.

Если наш проект не требует этого, мы можем отключить его в пользовательском WebSecurityConfigurerAdapter :

@Configuration
public class SpringBootSecurityConfiguration
extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}

4. Включите защиту CSRF с помощью REST API

4.1. Весенняя конфигурация

Если нашему проекту требуется защита CSRF, мы можем отправить токен CSRF с файлом cookie с помощью CookieCsrfTokenRepository в пользовательском WebSecurityConfigurerAdapter .

Мы должны установить для флага HTTP-only значение false , чтобы иметь возможность получить его из нашего клиента JavaScript:

@Configuration
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}

После перезапуска приложения наши запросы получают ошибки HTTP, что означает, что защита CSRF включена.

Мы можем подтвердить, что эти ошибки выдаются классом CsrfFilter , настроив уровень журнала на DEBUG:

<logger name="org.springframework.security.web.csrf" level="DEBUG" />

Он будет отображать:

Invalid CSRF token found for http://...

Кроме того, мы должны увидеть в нашем браузере, что присутствует новый файл cookie XSRF-TOKEN .

Давайте добавим пару строк в наш REST-контроллер, чтобы также записывать информацию в наши журналы API:

CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
LOGGER.info("{}={}", token.getHeaderName(), token.getToken());

4.2. Конфигурация клиента

В клиентском приложении файл cookie XSRF-TOKEN устанавливается после первого доступа к API. Мы можем получить его с помощью регулярного выражения JavaScript:

const csrfToken = document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, '$1');

Затем мы должны отправлять токен на каждый запрос REST, который изменяет состояние API: POST, PUT, DELETE и PATCH.

Spring ожидает получить его в заголовке X-XSRF-TOKEN . Мы можем просто установить его с помощью JavaScript Fetch API:

fetch(url, {
method: 'POST',
body: JSON.stringify({ /* data to send */ }),
headers: { 'X-XSRF-TOKEN': csrfToken },
})

Теперь мы видим, что наш запрос работает, а ошибка «Неверный токен CSRF» исчезла в журналах REST API.

Поэтому злоумышленники не смогут провести CSRF-атаку . Например, скрипт, пытающийся выполнить тот же запрос с мошеннического веб-сайта, получит ошибку «Неверный токен CSRF» .

В самом деле, если пользователь не посетил фактический веб-сайт сначала, файл cookie не будет установлен, и запрос не будет выполнен.

5. Вывод

В этой статье мы рассмотрели различные контексты, в которых возможны или нет атаки CSRF на REST API.

Затем мы узнали, как включить или отключить защиту CSRF с помощью Spring Security.