Внедрение защиты от CSRF-атак в приложение с cookie-сессиями начинается с обязательной настройки заголовка SameSite=Strict или Lax на всех ответных запросах сервера. Это предотвращает отправку куки при переходе по внешним ссылкам, что является первым барьером для атак. Второй критический шаг — генерация уникального CSRF-токена для каждого сеанса и его проверка в каждом POST/PUT/DELETE запросе перед выполнением действия. Токен хранится на сервере (в сессии или базе данных) и сравнивается с тем, что был отправлен клиентом через скрытое поле формы или заголовок запроса. При использовании куки-сессий атрибут Secure должен быть включён в продакшене для передачи только по HTTPS, а HttpOnly обязателен, чтобы скрипты не могли читать токен. Если приложение использует JSON вместо HTML-форм, то CSRF-токен передаётся через заголовок X-CSRF-Token или аналогичный, который сервер обязан проверять для всех мутационных запросов. Внедрение защиты требует изменения архитектуры обработки запросов: добавление middleware или фильтра, который перехватывает входящий запрос, извлекает токен и валидирует его перед логикой бизнес-слоя. При смене сессии (логин/выход) сервер должен уничтожать старый токен CSRF, чтобы предотвратить атаку через старые куки, если они не были удалены клиентом. Тестирование защиты включает отправку запросов из разных доменов и проверку того, что сервер отвергает их без валидного токена, даже если куки присутствуют.
Настройка заголовка SameSite для защиты cookie-сессий
Защита от межсайтовых запросов с подделкой запроса (CSRF) начинается с правильной конфигурации атрибутов безопасности на уровне HTTP-заголовков. Ключевым элементом здесь является заголовок Set-Cookie, который сервер отправляет браузеру вместе с куки-сессией. В этом заголовке должен быть явно указан атрибут SameSite со значением Strict или Lax. Если этот параметр отсутствует или установлен как None без обязательного флага Secure, браузер может отправить куки при переходе по ссылке из другого домена, что открывает путь для атак.
Значение Strict запрещает отправку куки в любых запросах, инициированных не скриптом текущего сайта, а только при прямом взаимодействии с самим ресурсом. Это полностью блокирует возможность злоумышленника использовать форму жертвы для отправки мутационных запросов на ваш сервер. Значение Lax позволяет отправку куки при навигации пользователя (переход по ссылке), но запрещает их передачу в скриптовых запросах, таких как fetch или AJAX. Выбор между этими значениями зависит от архитектуры вашего приложения: если оно не требует передачи сессии через iframe или редиректы из внешних источников, выбирайте Strict.
Для реализации этой защиты необходимо изменить логику формирования заголовков на всех контроллерах и маршрутизаторах приложения. В фреймворках типа Django это делается через настройки middleware, в Spring Boot — через конфигурацию CookieSerializer, а в Node.js/Express — через пакет cookie-parser или аналогичные библиотеки. Важно убедиться, что эта настройка применяется ко всем ответным запросам, включая редиректы и ответы на статические файлы, если они содержат сессионную информацию.
Практический пример настройки в коде: при создании ответа сервер должен явно добавлять флаг SameSite. В Python-фреймворке Flask это выглядит как добавление параметра samesite=True в функции make_response или через конфигурацию приложения. В Java-приложениях необходимо настроить сериализатор куки, чтобы он автоматически включал этот атрибут для всех новых сессий.
Ошибкой является игнорирование этого шага при переходе на HTTPS. Даже если соединение защищено шифрованием, отсутствие флага SameSite позволяет атакующему перехватить куки через XSS-уязвимость или заставить браузер отправить их в запросе из другого сайта. Кроме того, атрибут Secure должен быть включен для всех сессионных куки в продакшене, чтобы они передавались только по защищенным каналам. Для локальных тестов этот флаг можно отключить, но это не должно влиять на логику защиты CSRF.
Проверка корректности настройки производится через инструменты анализа трафика, такие как curl или браузерные расширения типа Live HTTP Headers. Отправьте запрос к эндпоинту входа и проверьте заголовок Set-Cookie в ответе. Убедитесь, что строка содержит SameSite=Strict (или Lax) и Secure=true. Если флаг отсутствует, браузер по умолчанию может вести себя непредсказуемо в зависимости от версии и типа запроса.
Внедрение этой меры не требует изменения клиентского кода или логики бизнес-процессов, но меняет поведение браузера на стороне клиента. Пользователи не заметят разницы в работе интерфейса, если только приложение не использует редиректы из внешних сервисов, которые ранее работали с куками. Однако безопасность повышается за счет исключения целого класса атак без потери функциональности.
Если ваше приложение интегрируется с внешними системами через OAuth или другие протоколы, где используются редиректы, настройка SameSite=Lax может быть предпочтительнее, чтобы не разрывать цепочку авторизации. В таком случае необходимо дополнительно реализовать проверку CSRF-токена для всех мутационных запросов, так как защита только заголовком SameSite в режиме Lax не является полной.
Результат внедрения этой меры оценивается по отсутствию успешных атак типа CSRF в тестовой среде. Проведите тесты, отправляя запросы из других доменов с куками вашего приложения включенными. Сервер должен отвергать такие запросы, если они не содержат валидного токена и не приходят с разрешенного источника. Это подтверждает, что браузер корректно применяет политику SameSite.
Генерация и проверка CSRF-токенов для каждого сеанса
Защита форм от атак типа CSRF строится на принципе уникальности. Для каждого сеанса работы пользователя сервер должен создавать новый токен, который привязывается к его сессии. Этот токен генерируется криптографически стойким способом (например, через `random` или `crypto.randomBytes` в Node.js) и хранится в памяти сервера или базе данных вместе со статусом сессии.
Проверка токена происходит на стороне сервера перед выполнением любого мутационного действия. Когда браузер отправляет POST, PUT или DELETE запрос, он должен содержать этот токен — либо в скрытом поле формы (для HTML), либо в заголовке `X-CSRF-Token` (для API). Middleware приложения перехватывает входящий запрос, извлекает значение токена и сравнивает его с тем, что хранится в контексте текущей сессии.
Если значения совпадают, сервер разрешает выполнение действия. Если токен отсутствует или не соответствует — запрос отклоняется с кодом 403 Forbidden. Важно: проверка должна быть строгой. Даже если браузер отправил куки (что возможно при настройке `SameSite=None`), отсутствие валидного токена должно блокировать запрос.
В случае использования JSON-интерфейсов без HTML-форм, токен передаётся через HTTP-заголовок. Сервер обязан проверять этот заголовок для всех запросов, изменяющих состояние системы. Не полагайтесь на куки как единственный источник токена в API, так как злоумышленник может подделать куки, но не сможет сгенерировать правильный токен без доступа к серверу.
При смене состояния сессии (например, при выходе из системы или истечении времени жизни) сервер должен уничтожать старый токен. Если пользователь выйдет из аккаунта, а затем попытается отправить запрос с сохранённым в браузере токеном, сервер не найдёт соответствующей записи и заблокирует действие. Это предотвращает использование старых токенов после смены пароля или логина.
Оценка эффективности внедрения этой меры проводится через тестирование в изолированной среде. Запустите приложение и попробуйте отправить запросы на мутационные эндпоинты из другого домена (например, с локального сервера Ollama или стороннего инструмента), не очищая куки. Сервер должен возвращать ошибку 403 для всех таких попыток, если они не содержат корректного токена.
Обратите внимание на ограничения: хранение всех активных токенов в памяти может потребовать значительных ресурсов при высокой нагрузке. В таких случаях используйте базу данных или кэш (Redis) с TTL, привязанным к ID сессии. Также убедитесь, что генерация токенов не становится узким местом — для этого используйте аппаратное ускорение криптографических операций или готовые библиотеки.
Хранение и валидация CSRF-токена на сервере
Токен должен храниться на сервере (в сессии или базе данных) и сравниваться с тем, что был отправлен клиентом через скрытое поле формы или заголовок запроса. Это фундаментальный принцип защиты: браузер не может прочитать секретный токен из-за атрибута HttpOnly, поэтому проверка возможна только со стороны сервера.
При реализации механизма хранения выбирайте между двумя основными подходами в зависимости от архитектуры приложения. Первый вариант — хранение в сессии (например, в Redis или файловой системе). Сервер генерирует токен при создании сессии пользователя и привязывает его к её идентификатору. При обработке POST-запроса сервер извлекает токен из сессии по ID пользователя и сравнивает его с тем, что пришел в запросе. Второй вариант — хранение в базе данных. Это предпочтительнее для систем с высокой нагрузкой или сложной логикой авторизации. В таблице пользователей или отдельной таблице токенов хранится пара «ID сессии — токен». Сервер выполняет JOIN-запрос или прямое сравнение по ID сессии перед выполнением любого мутационного действия.
Сравнение токенов должно происходить строго синхронно. Никогда не кэшируйте результат проверки в памяти дольше одного цикла обработки запроса, так как это создаст окно уязвимости при перезагрузке сервера или смене состояния сессии. Алгоритм работы выглядит следующим образом: 1) Сервер получает входящий запрос с заголовком X-CSRF-Token или значением из скрытого поля формы. 2) Middleware извлекает токен из тела запроса. 3) Сервер ищет соответствующую запись в хранилище (сессия/БД). 4) Если токен совпадает — выполняется бизнес-логика. 5) Если токен отсутствует или не совпадает — возвращается ошибка 403 Forbidden, и действие блокируется.
Важно учитывать формат передачи токена. Для HTML-форм токен обычно помещается в скрытое поле <input type="hidden" name="csrf_token" value="…">. При отправке формы браузер автоматически включает это значение в тело запроса (Content-Type: application/x-www-form-urlencoded). Для API на базе JSON токен передается через заголовок X-CSRF-Token или, реже, как параметр body. Сервер должен уметь различать эти форматы и корректно извлекать токен независимо от типа контента запроса.
При смене состояния сессии (логин/выход) сервер обязан уничтожать старый токен CSRF. Если пользователь выходит из системы, запись о токене в базе данных или кэше должна быть удалена. Это предотвращает атаку через старые куки: злоумышленник, перехвативший куки после выхода пользователя, не сможет использовать сохраненный ранее токен для доступа к аккаунту. Аналогично, при смене пароля или изменении ключей шифрования сессии старые токены становятся невалидными.
Практический пример реализации на псевдокоде демонстрирует логику валидации:
function validateCsrfToken(sessionId, incomingToken) {
const storedToken = sessionStore.get(sessionId);
if (!storedToken || storedToken !== incomingToken) {
// Токен не найден или не совпадает — блокируем запрос
return false;
}
// Удаляем токен после успешного использования (опционально, для одноразовых токенов)
// sessionStore.remove(sessionId);
return true;
}
// Пример обработки POST-запроса
app.post('/api/update-profile', (req, res) => {
const isValid = validateCsrfToken(req.session.id, req.body.csrf_token);
if (!isValid) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
// Выполнение бизнес-логики
updateProfile(req.user, req.body);
});
Ошибки при внедрении часто связаны с неправильным управлением жизненным циклом токенов. Например, если сервер не очищает токен после успешной операции, злоумышленник может перехватить куки и использовать валидный токен повторно (атака повторного воспроизведения). Чтобы минимизировать риски, используйте одноразовые токены или сбрасывайте их сразу после успешной обработки запроса. Также избегайте хранения чувствительных данных в том же поле с токеном — это нарушает принцип разделения ответственности.
Для высоконагруженных систем рекомендуется использовать распределенное хранилище (Redis Cluster), где токен привязан к ID сессии и имеет TTL, равный времени жизни сессии. Это гарантирует автоматическое удаление неактивных токенов без необходимости ручной очистки. При выборе базы данных убедитесь, что индексируемые поля позволяют быстро находить запись по ID сессии, чтобы проверка токена занимала менее 1 мс.
Результат внедрения корректного механизма хранения и проверки токенов оценивается по количеству заблокированных несанкционированных запросов. В идеале система должна отвергать все попытки изменения данных из внешних источников без валидного токена, даже если куки присутствуют в заголовках. Регулярно проводите тесты с использованием инструментов типа Burp Suite или OWASP ZAP: пытайтесь отправить POST-запрос на целевой ресурс через другой домен или с подмененными куками. Если сервер возвращает 403 — защита работает.
Настройка атрибутов безопасности cookie и валидация токенов
При работе с cookie-сессиями безопасность зависит от правильной конфигурации атрибутов самого файла cookie. Критически важно разделить настройки для среды разработки и продакшена. В локальных тестах атрибут Secure часто отключают, чтобы куки передавались по HTTP, но в реальной среде он обязателен: без него браузер отправит чувствительные данные при переходе на незашифрованный сайт-перехватчик.
Параллельно с этим необходимо установить флаг HttpOnly. Он запрещает JavaScript читать содержимое cookie через объект document.cookie. Это предотвращает кражу токена через XSS-уязвимости, даже если в приложении есть ошибки на клиенте. Токен CSRF должен быть доступен только для отправки сервером (через заголовки или скрытые поля), но не для чтения скриптами.
Для реализации этих требований используйте middleware или конфигурацию фреймворка (например, express-session для Node.js, Django.middleware для Python). Настройте параметры при создании cookie: Secure=true для продакшена, HttpOnly=true, SameSite=Strict или Lax. В коде это выглядит как явное указание атрибутов при ответе сервера.
Проверка токена происходит на стороне сервера до выполнения любой логики изменения данных. Middleware перехватывает запрос, извлекает токен из заголовка X-CSRF-Token (для API) или скрытого поля формы (для веб), сравнивает его с тем, что хранится в сессии, и только при совпадении пропускает запрос дальше. Если проверка не пройдена — возвращайте ошибку 403 Forbidden.
Особое внимание уделите ротации токенов. При каждом успешном входе или изменении пароля сервер должен генерировать новый токен и уничтожать старый. Это защищает от атак, когда злоумышленник перехватывает старую сессию: даже если он сохранит куки, токен внутри них уже будет недействительным для новых действий.
Важно понимать ограничения подхода. Защита от CSRF не спасает от других уязвимостей, таких как SQL-инъекции или XSS. Если в приложении есть возможность выполнить скрипт (XSS), злоумышленник может украсть куки с флагом HttpOnly=false. Поэтому установка HttpOnly является обязательным условием для любого приложения с аутентификацией.
Оценка эффективности защиты строится на результатах пентеста. Запустите сканер уязвимостей (например, OWASP ZAP) и попробуйте выполнить POST-запрос на целевой ресурс через другой домен или используя инструменты отладки браузера для подмены заголовков. Если сервер корректно отвергает такие запросы кодом 403, а не обрабатывает их как валидные — защита внедрена правильно.
Защита API-интерфейсов и JSON-клиентов от CSRF
Когда приложение отказывается от классических HTML-форм в пользу обмена данными через JSON, механизм защиты от межсайтовых запросов (CSRF) трансформируется. В веб-браузере браузер автоматически отправляет куки при переходе по ссылке на том же домене, но при работе с API часто используются инструменты, которые не сохраняют куки или работают в контексте другого домена. Однако главная опасность возникает тогда, когда злоумышленник подменяет заголовок `Cookie` в запросе к вашему серверу, используя куки жертвы, полученные из-за неправильной настройки атрибутов (например, отсутствие HttpOnly). В случае с JSON-клиентами или API-интерфейсами браузер не может автоматически отправить CSRF-токен, так как он отсутствует в заголовках по умолчанию. Поэтому защита строится исключительно на явной проверке уникального токена, который клиент должен передать вручную.
В отличие от HTML-форм, где токен можно скрыть в поле `<input type="hidden">`, при работе с JSON-запросами (например, через `fetch` или `axios`) токен нужно явно добавить в заголовок запроса. Стандартным решением является использование заголовка `X-CSRF-Token` или `X-XSRF-TOKEN`. Сервер обязан извлекать этот токен из заголовка и сравнивать его с тем, что хранится на сервере (в сессии пользователя или в базе данных). Если заголовок отсутствует или значение не совпадает — запрос отвергается кодом 403 Forbidden. Это правило применяется ко всем мутационным операциям: POST, PUT, PATCH, DELETE.
Реализация такой защиты требует изменения логики обработки входящих запросов на уровне middleware или фильтра. На каждом этапе валидации сервер должен извлекать токен из заголовка `X-CSRF-Token` и проверять его корректность перед передачей запроса бизнес-логике. Важно понимать, что если вы используете куки для сессии, но не настроили атрибут `SameSite=Strict`, браузер может отправить куки при переходе по ссылке из другого домена (например, из фишингового сайта). В этом случае злоумышленник может подменить заголовок `Cookie` в своем запросе, и сервер примет его как валидный. Поэтому даже при использовании JSON-клиентов критически важно настроить атрибуты куки: `HttpOnly`, чтобы скрипты не могли читать токен, и `Secure`, чтобы куки передавались только по HTTPS.
Для интеграции с внешними сервисами или API-клиентами, которые работают в разных контекстах, рекомендуется использовать механизм двойной проверки. Клиент должен сначала отправить запрос на эндпоинт `/api/csrf-token` для получения нового токена, а затем использовать его в последующих мутационных запросах. Токен имеет ограниченное время жизни и уничтожается сервером при смене сессии (логин/выход) или по истечении таймаута. Это предотвращает атаку через старые куки, если они не были удалены клиентом. Также стоит учитывать, что некоторые библиотеки для работы с API автоматически добавляют заголовок `X-CSRF-Token` только при наличии соответствующей конфигурации, поэтому проверьте документацию используемых инструментов.
Тестирование защиты JSON-интерфейсов включает отправку запросов без заголовка `X-CSRF-Token` или с подмененным значением. Используйте инструменты отладки браузера или специализированные сканеры (например, OWASP ZAP) для проверки того, что сервер отвергает такие запросы кодом 403. Если сервер обрабатывает запрос без токена как валидный — защита не работает. Также проверьте, что при переходе по ссылке из другого домена браузер не отправляет куки вашего приложения, если атрибут `SameSite` настроен корректно. Ошибки часто возникают при попытке использовать один и тот же токен для всех типов запросов или при игнорировании проверки токена для PUT/PATCH операций.
Практический пример реализации на уровне middleware (например, в Node.js или Python): сервер перехватывает входящий запрос, извлекает токен из заголовка `X-CSRF-Token`, проверяет его наличие и валидность. Если проверка проходит — запрос передается дальше, иначе возвращается ошибка 403. Для динамических приложений токены могут генерироваться на основе случайных чисел или хешей сессии. Важно не хранить токены в куках без атрибута `HttpOnly`, так как это позволит JavaScript-скриптам читать их и использовать для атак. Если вы используете библиотеки, которые автоматически управляют куками, убедитесь, что они корректно обрабатывают атрибуты безопасности.
Ограничения подхода заключаются в том, что он требует участия клиента: клиент должен явно передавать токен, а не полагаться на автоматическую отправку браузера. Это усложняет интеграцию с простыми скриптами или устаревшими клиентами, которые не поддерживают заголовки `X-CSRF-Token`. Однако это компенсируется высокой надежностью защиты даже при наличии уязвимостей в настройках куки. Если вы используете JSON-клиенты для внутренних сервисов внутри одного домена, можно отключить проверку CSRF, но только если уверены, что трафик защищен и нет риска перехвата заголовков. В публичных API проверка обязательна.
Итоговая рекомендация: внедряйте проверку токена `X-CSRF-Token` для всех мутационных запросов к API, независимо от того, используются ли HTML-формы или JSON. Настройте атрибуты куки (`HttpOnly`, `Secure`, `SameSite`) так, чтобы браузер не отправлял их при переходе по ссылкам из других доменов. Регулярно тестируйте защиту с помощью сканеров уязвимостей и симуляции атак через подмену заголовков. Это обеспечит безопасность приложения даже в условиях сложных угроз.
Изменение архитектуры обработки запросов: внедрение middleware для валидации CSRF-токенов
Внедрение защиты от CSRF невозможно без перестройки логики обработки входящих запросов. Сервер должен перехватывать каждый POST, PUT или DELETE запрос до того, как он достигнет бизнес-логики. Для этого создаётся промежуточный слой — middleware (или фильтр), который работает на уровне HTTP-сервера или фреймворка. Его задача: извлечь токен из заголовка запроса или скрытого поля формы и сравнить его с ожидаемым значением, хранящимся в сессии пользователя.
Если проверка токена проходит успешно, запрос передаётся дальше. Если нет — сервер возвращает ошибку 403 Forbidden или 401 Unauthorized и блокирует выполнение действия. Это предотвращает выполнение несанкционированных команд даже при наличии у злоумышленника доступа к куки-сессии.
Для реализации такого middleware необходимо определить, откуда берётся токен. В HTML-формах он передаётся в заголовке `X-CSRF-Token` или скрытом поле `<input type="hidden">`. При работе с API на JSON данные идут только в теле запроса или заголовках. Middleware должен уметь работать с обоими форматами: извлекать токен из заголовка, если он там есть, и проверять его корректность.
Хранение ожидаемых токенов требует внимания к объёму данных. Если приложение поддерживает тысячи одновременных сессий, хранить все токены в оперативной памяти может быть накладно. В таких случаях используют базы данных или специализированные хранилища сессий (например, Redis). При этом важно обеспечить быструю проверку: сложность сравнения не должна превышать миллисекунды.
При смене состояния сессии — входе в систему, выходе или истечении времени жизни — сервер обязан уничтожать старые токены. Если пользователь вышел из аккаунта, но куки остались на устройстве, токен должен стать недействительным мгновенно. Это предотвращает атаку через сохранённые в браузере старые сессии. Реализация этого механизма требует правильной логики очистки данных при событиях `logout` или `session_destroy`.
Тестирование внедрения защиты должно включать симуляцию атак из разных доменов. Отправьте запросы на мутационные эндпоинты с подменёнными заголовками и проверьте, что сервер отвергает их. Используйте инструменты вроде Burp Suite или специальные скрипты для автоматизации проверки. Убедитесь, что даже при наличии куки-сессии без валидного токена действие не выполняется.
Ошибки при внедрении часто связаны с неправильной конфигурацией атрибутов куки или игнорированием JSON-запросов. Если атрибут `Secure` не установлен на продакшене, токен может быть перехвачен через незашифрованные соединения. Если middleware проверяет только HTML-формы и пропускает API-запросы, приложение остаётся уязвимым. Проверяйте каждый эндпоинт, который меняет состояние данных.
Практический пример реализации на уровне middleware: функция принимает запрос, извлекает токен из заголовка `X-CSRF-Token`, ищет его в хранилище сессий по идентификатору пользователя. Если совпадение найдено — возвращает флаг успеха. Если нет или токен просрочен — блокирует запрос. Логирование попыток подмены токенов помогает выявлять атаки на ранних стадиях.
Важно учитывать, что middleware должен работать независимо от языка программирования и фреймворка. Принципы валидации универсальны: перехват, извлечение, сравнение, решение. Это позволяет адаптировать защиту под любые стеки технологий. При миграции с одного фреймворка на другой достаточно перенести логику middleware, не меняя бизнес-логику.
Ограничения подхода связаны с производительностью при большом количестве запросов и сложностью управления состоянием в распределённых системах. В кластерах серверов каждый узел должен иметь доступ к единому хранилищу токенов (например, через Redis). Иначе один узел может отклонить валидный токен, созданный на другом. Это требует дополнительной настройки синхронизации или использования распределённых сессий.
Управление жизненным циклом токенов при смене состояния сессии
При переходе пользователя между состояниями — входе, выходе или смене роли — сервер обязан уничтожать старый CSRF-токен. Это критическая мера безопасности: если клиент не очистит куки самостоятельно (например, через кнопку «Выйти»), злоумышленник, получивший доступ к старому токену, сможет выполнить действия от имени пользователя даже после того, как он авторизовался заново или покинул систему. Устаревшие токены становятся вектором атаки типа replay-атаки, когда вредоносный скрипт использует перехваченную сессию для доступа к ресурсам.
Механизм реализации строится на привязке токена к текущей сессии в памяти сервера или базе данных. При успешной авторизации генерируется новый токен и записывается в хранилище, а предыдущий помечается как невалидный или физически удаляется. Если приложение хранит токены в Redis, необходимо явно удалить ключ старого токена перед созданием нового. В системах с хранением в базе данных (например, PostgreSQL) требуется выполнить UPDATE запрос для установки флага is_revoked = true или DELETE старой записи. Важно: удаление должно происходить асинхронно только если это не влияет на время отклика, но для критических операций лучше использовать синхронное завершение транзакции.
В случае выхода из системы (logout) сервер должен инициировать процесс очистки немедленно. Это делается через middleware, который перехватывает запрос на выход и вызывает функцию уничтожения токена. Если пользователь просто закрывает браузер, токен может остаться активным до истечения срока действия куки (например, 30 дней). Поэтому кнопка «Выйти» не должна быть формальностью — она должна гарантированно аннулировать сессию и все связанные токены. Для повышения безопасности можно также очищать куки клиента через заголовок Set-Cookie: Session=; Path=/; HttpOnly; Secure; SameSite=Lax, что заставляет браузер удалить локальное хранилище.
При реализации в распределённых системах (кластеры серверов) проблема усугубляется: если один узел уничтожил токен, а другой ещё не получил об этом информацию, возможна ситуация, когда запрос с этим токеном будет принят. Решение — использование общего хранилища состояний (Redis, Memcached), где запись о невалидности токена доступна всем узлам кластера. Если централизованное хранилище недоступно, следует применять стратегию короткого срока жизни токенов и обязательной проверки их статуса перед использованием. В таких случаях также рекомендуется использовать ротацию токенов: при каждом новом действии выдаётся новый токен, а старый автоматически истекает.
Ошибки при реализации часто связаны с тем, что разработчики полагаются на автоматическое истечение срока действия куки вместо явного уничтожения. Это создаёт окно уязвимости в течение всего времени жизни куки. Также распространена практика хранения токенов только в памяти без синхронизации между серверами, что делает систему уязвимой при масштабировании. Правильный подход — всегда явно управлять жизненным циклом токена: создавать, использовать и уничтожать его в строго определённых точках приложения. Тестирование должно включать сценарии, где пользователь выходит из системы, а затем пытается выполнить действие через старый токен (например, через перехваченную куку или кэш браузера). Сервер должен отвергать такой запрос с ошибкой 403 Forbidden.
Тестирование и валидация CSRF-защиты
Проверка эффективности защиты от CSRF начинается не с автоматических сканеров, а с ручного моделирования атак из внешних источников. Ваша цель — убедиться, что сервер корректно отвергает запросы, пришедшие из доменов, отличных от вашего основного, даже если браузер автоматически прикрепил куки-сессию к запросу.
Основной метод проверки: используйте инструменты типа Burp Suite или Postman для отправки POST-запросов с подмененным заголовком Origin или Referer на адрес стороннего ресурса (например, testsite.ru). В запросе укажите валидную куки-сессию вашего приложения. Если сервер вернет ошибку 403 Forbidden или не выполнит действие (не обновит баланс, не отправит письмо), защита работает. Если же действие произошло — значит, токен отсутствует в заголовке запроса или проверка токена реализована с ошибками.
Второй критический сценарий: атака через кэш браузера. Пользователь авторизуется на вашем сайте, получает куки и CSRF-токен. Затем он переходит по ссылке из письма или мессенджера (например, в Telegram), которая ведет на форму злоумышленника. Браузер отправляет куки автоматически, но заголовок Origin указывает на домен атакующего. Сервер должен сравнить этот заголовок с ожидаемым и отвергнуть запрос, если они не совпадают или если токен в теле формы/заголовке отсутствует.
При тестировании важно учитывать работу с JSON-интерфейсами. Если ваше приложение использует REST API без HTML-форм, CSRF-токен передается через заголовок X-CSRF-Token. Проверьте, что сервер требует этот заголовок для всех методов PUT, DELETE и POST. Отправьте запрос без него — сервер должен отклонить его. Также протестируйте случай, когда токен в заголовке не совпадает с тем, что хранится на сервере (например, измененный хеш).
Не забывайте проверять поведение при смене состояния сессии. Выход из системы должен уничтожать токен CSRF на сервере. Если пользователь выйдет из аккаунта, а затем попытается выполнить действие через сохраненную в кэше куки (которая еще не истекла по времени), сервер обязан отказать в доступе. Это предотвращает использование старых токенов после смены пароля или роли пользователя.
Используйте инструменты автоматизированного тестирования, такие как OWASP ZAP или Burp Suite Professional, для проведения пентеста. Настройте их на поиск уязвимостей типа Cross-Site Request Forgery. Запустите сканирование против вашего локального сервера (например, Ollama или бизнес-приложения). Обратите внимание на предупреждения: если сканер обнаруживает уязвимость, проверьте логи сервера — возможно, проверка токена реализована, но не учитывает все сценарии (например, запросы из iframe).
Вопросы и ответы
Почему атрибут Secure обязателен в продакшене?
Без флага Secure браузер отправляет куки по незашифрованному соединению (HTTP). Это позволяет перехватчикам на промежуточных серверах или публичных Wi-Fi читать сессионные данные. В локальной разработке отключение этого атрибута упрощает тестирование, но в реальной среде оно критично.
Как защитить API-запросы, если они не используют HTML-формы?
Вместо скрытого поля формы токен передаётся через заголовок запроса (например, X-CSRF-Token или X-XSRF-TOKEN). Сервер должен проверять наличие этого заголовка и валидировать его для всех мутационных операций (POST, PUT, DELETE), даже если клиент использует JavaScript-клиент.
Что делать, если пользователь не вышел из системы, но сессия истекла?
При завершении сессии сервер должен уничтожать связанные с ней CSRF-токены. Если токен хранится в базе данных или кэше, запись должна быть удалена или помечена как недействительной. Это предотвращает использование старых токенов после перезахода пользователя.
// Пример middleware для проверки CSRF-токена
const csrf = require('csurf');
// Настройка токенов в Redis (пример)
const redisStore = require('connect-redis')(session);
app.use(session({
store: new redisStore(),
secret: 'your-secret-key',
resave: false,
saveUninitialized: true
}));
// Генерация токена при создании сессии
app.post('/login', (req, res) => {
const token = crypto.randomBytes(32).toString('hex');
req.session.csrfToken = token;
// Сохранение в Redis или БД: redis.set(`csrf:${req.session.id}`, token, EX, sessionTimeout);
res.redirect('/dashboard');
});
// Middleware для проверки токена на мутационных запросах
app.use(['/api/update', '/api/delete'], (req, res, next) => {
const incomingToken = req.body.csrf_token || req.headers['x-csrf-token'];
const storedToken = req.session.csrfToken;
if (!incomingToken || incomingToken !== storedToken) {
return res.status(403).json({ error: 'CSRF token mismatch' });
}
// Удаление токена после использования (опционально)
delete req.session.csrfToken;
next();
});


