Что такое CSRF и как защитить от него веб-приложение
Основы3 минРедакция Innovialabs
Коротко
CSRF — атака, при которой чужой сайт заставляет браузер пользователя отправить запрос в ваше приложение вместе с его cookie. Защита — CSRF-токен или проверка Origin для всех изменяющих запросов плюс SameSite-cookie как дополнительный слой.
Что такое CSRF
CSRF (Cross-Site Request Forgery, подделка межсайтового запроса) — атака, при которой злоумышленник заставляет браузер вошедшего пользователя отправить запрос в ваше приложение. Браузер автоматически прикладывает к запросу cookie сессии, и сервер считает действие легитимным: меняется email, переводятся деньги, удаляются данные.
Ключевая деталь: атакующий не видит ответ и не крадёт cookie. Ему достаточно, чтобы запрос выполнился.
Пример поддельной формы
Допустим, в банке есть форма смены email, которая отправляет POST /account/email. Злоумышленник размещает на своём сайте такую страницу:
<form action="https://bank.example/account/email" method="POST" id="f">
<input type="hidden" name="email" value="attacker@evil.example">
</form>
<script>document.getElementById('f').submit();</script>
Пользователь, который залогинен в банке, открывает эту страницу — форма отправляется сама. Если сервер проверяет только cookie, email меняется, а дальше атакующий восстанавливает пароль на свой адрес.
Уязвимы любые действия, которые меняют состояние и опираются только на cookie: формы, GET-ссылки с побочными эффектами, API без собственной проверки.
Способы защиты
CSRF-токен (synchronizer token)
Сервер генерирует случайный непредсказуемый токен, привязывает его к сессии и вставляет в каждую форму. При POST, PUT, PATCH, DELETE сервер сверяет токен из тела запроса или заголовка с сохранённым.
Чужой сайт не может прочитать вашу страницу (это запрещает Same-Origin Policy), поэтому не знает токен. Это классический и самый надёжный вариант — большинство фреймворков (Django, Laravel, Rails, Spring Security) включают его из коробки.
Double-submit cookie
Подходит, когда нет серверного хранилища сессий. Токен кладётся в cookie и одновременно отправляется в заголовке или поле формы; сервер проверяет, что значения совпадают. Важно: токен должен быть подписан (HMAC) и привязан к сессии, иначе атакующий, способный записать cookie через поддомен, обойдёт защиту.
Проверка Origin и Referer
Браузер добавляет заголовок Origin к межсайтовым и POST-запросам. Сервер может отклонять изменяющие запросы, если Origin не входит в список ваших доменов. Если Origin отсутствует, можно смотреть на Referer. Современные браузеры также шлют Sec-Fetch-Site, по которому легко отличить same-origin от cross-site.
Это хороший дополнительный слой и простой вариант для API, но запросы без этих заголовков нужно обрабатывать строго — отклонять.
SameSite cookie
Атрибут SameSite говорит браузеру, отправлять ли cookie при межсайтовых запросах:
| Значение | Поведение | Защита от CSRF |
|---|---|---|
Strict | cookie не отправляется ни с какими межсайтовыми запросами | сильная |
Lax | отправляется только при переходе по ссылке (GET верхнего уровня) | от форм POST защищает |
None | отправляется всегда (требует Secure) | нет |
Когда SameSite достаточно
SameSite=Lax или Strict закрывает большую часть сценариев, но полагаться только на него стоит лишь при всех условиях сразу:
- все изменяющие действия идут через
POST/PUT/DELETE, а не черезGET; - у вас нет поддоменов, которые контролирует кто-то другой: SameSite считает «своим» весь сайт (
*.example.com), а не конкретный origin; - cookie явно выставлены с
SameSite, а не оставлены на умолчаниях браузера; - пользователи работают в браузерах, которые поддерживают атрибут.
Если хотя бы одно не выполняется — добавьте токены или проверку Origin. Для банков, админок и платёжных операций правильный выбор — токен плюс SameSite.
Частые ошибки
- Изменение данных через
GET(/delete?id=5) — такие ссылки срабатывают даже приSameSite=Lax. - Токен проверяется, только если он передан: отсутствие токена должно означать отказ.
- Один токен на всех пользователей или токен в URL, который утекает через логи и
Referer. - CORS с
Access-Control-Allow-Origin: *и credentials в надежде, что это «защищает» — CORS управляет чтением ответов, а не отправкой запросов. - Защищены формы, но забыт JSON API, который принимает
text/plainилиapplication/x-www-form-urlencoded.
Подробный разбор мер — в OWASP CSRF Prevention Cheat Sheet.