Навигация

CSRF: механика атаки и линии защиты

CSRF (Cross-Site Request Forgery) — атака, при которой злоумышленник заставляет браузер авторизованного пользователя выполнить нежелательный запрос к доверенному сайту. Ключевая особенность: атакующий не крадёт пароль и не ломает сервер — он использует нормальное поведение браузера, автоматически прикрепляющего cookies к запросам. Статья разбирает механику атаки, три линии защиты (SameSite, CSRF-токены, Origin), паттерн double submit cookie и специфику React, Vue, Next.js и Nuxt.

Содержание

  1. Что такое CSRF и почему он работает
  2. Классическая атака по шагам
  3. Почему CSRF не умер в эпоху SPA и JSON API
  4. SameSite cookies: защита на уровне браузера
  5. CSRF-токены: золотой стандарт
  6. Double Submit Cookie pattern
  7. Origin, Referer и дополнительные проверки
  8. CSRF в React, Vue, Next.js и Nuxt
  9. Когда CSRF-защита не нужна
  10. Практический чек-лист
  11. Терминология CSRF
  12. Ключевые тезисы для интервью
  13. Заключение
  14. Полезные ссылки

Что такое CSRF и почему он работает

CSRF (Cross-Site Request Forgery, подделка межсайтовых запросов) — это атака, при которой злоумышленник заставляет браузер авторизованного пользователя выполнить нежелательное действие на доверенном сайте.

Ключевое слово — браузер. Злоумышленник не крадёт пароль, не ломает сервер, не обходит CORS. Он использует нормальное поведение браузера: если у вас есть cookie для bank.com, браузер автоматически прикрепляет её к любому запросу на bank.com, независимо от того, кто инициировал запрос.

Аналогия: вы оставили доверенное письмо у двери курьеру с просьбой передать посылку. Злоумышленник подделывает такое же письмо и даёт его курьеру. Курьер не проверяет, от кого на самом деле письмо — он видит вашу подпись (cookie) и выполняет просьбу.


Классическая атака по шагам

  1. Пользователь входит в bank.com и получает сессионную cookie.
  2. Не выходя из системы, пользователь открывает evil.com в другой вкладке.
  3. evil.com содержит форму или JavaScript, который отправляет запрос на bank.com:
<!-- Форма автоматически отправляется при загрузке -->
<form action="https://bank.com/transfer" method="POST" style="display:none">
  <input name="to" value="hacker_account" />
  <input name="amount" value="10000" />
</form>
<script>document.forms[0].submit();</script>
  1. Браузер отправляет POST-запрос на bank.com и автоматически прикрепляет cookie.
  2. Сервер bank.com видит валидную сессию и выполняет перевод.

Важно: злоумышленник не видит ответ сервера из-за Same-Origin Policy. Но ему это и не нужно — важно, чтобы запрос дошёл и выполнился.


Почему CSRF не умер в эпоху SPA и JSON API

Частое заблуждение: «Мы используем React и fetch с JSON, формы — это прошлый век». Но CSRF работает и с JSON, если выполнены два условия:

  1. Аутентификация построена на cookies.
  2. Сервер принимает cross-origin запросы с нужным Content-Type.

JSON CSRF

fetch('https://bank.com/api/transfer', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ to: 'hacker', amount: 10000 }),
});

До появления SameSite=Lax браузер отправлял cookie и с таким запросом.

Простые Content-Type

Если сервер принимает text/plain или application/x-www-form-urlencoded, злоумышленник может отправить JSON-подобное тело через HTML-форму:

<form action="https://bank.com/api/transfer" method="POST" enctype="text/plain">
  <input name='{"to":"hacker","amount":10000,"ignore":"' value='"}' />
</form>

Это создаёт валидный JSON, который некоторые серверы могут распарсить.


SameSite cookies: защита на уровне браузера

SameSite — атрибут cookie, который говорит браузеру, когда отправлять cookie при cross-origin запросах.

Значения

ЗначениеПоведение
StrictCookie отправляется только при запросах внутри того же сайта. Переход по ссылке из email не авторизует пользователя.
LaxCookie отправляется при top-level навигации (GET-запросы по ссылке), но не при cross-site POST/iframe/fetch.
NoneCookie отправляется всегда. Требует Secure. Используется для embedded-виджетов и SSO.

С 2020 года Chrome и другие браузеры делают SameSite=Lax значением по умолчанию.

Пример

// Next.js — установка cookie
import { cookies } from 'next/headers';

cookies().set('session_id', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  path: '/',
});
// Express
res.cookie('session_id', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
});

Почему Lax — компромисс

Strict идеален с точки зрения безопасности, но может раздражать пользователей: если они перешли по ссылке из email, они будут выглядеть как неавторизованные. Lax защищает от POST-запросов CSRF, но не от GET-атак, где действие выполняется через URL.

Поэтому для критичных операций SameSite=Strict + CSRF-токен — лучшая комбинация.


CSRF-токены: золотой стандарт

Идея: сервер генерирует секретный токен, включает его в форму или страницу, а при мутации требует этот токен обратно. Злоумышленник не может узнать токен, потому что не может прочитать содержимое вашего сайта из-за Same-Origin Policy.

Классический flow

  1. Сервер генерирует токен и сохраняет его в сессии.
  2. Сервер вставляет токен в HTML-форму как скрытое поле.
  3. При отправке формы сервер сверяет токен из формы с токеном в сессии.
<form action="/transfer" method="POST">
  <input type="hidden" name="csrf_token" value="abc123..." />
  <!-- остальные поля -->
</form>

Для SPA и API

В SPA токен обычно отдаётся через отдельный endpoint или встраивается в HTML, а клиент отправляет его в заголовке:

// Сервер (Next.js)
export async function GET() {
  const token = generateCsrfToken();
  cookies().set('csrf_token', token, { httpOnly: true, sameSite: 'strict' });
  return Response.json({ csrfToken: token });
}

// Клиент
fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': csrfToken,
  },
  credentials: 'include',
  body: JSON.stringify(data),
});

Сервер сверяет заголовок с cookie. Cookie httpOnly защищает от XSS, а заголовок защищает от CSRF.


Альтернатива хранению токена в сессии: сервер устанавливает токен в cookie, а клиент отправляет его в заголовке или скрытом поле. Сервер проверяет, совпадают ли cookie и заголовок.

Set-Cookie: csrf_token=abc123; SameSite=Strict; Secure
X-CSRF-Token: abc123

Почему это работает: злоумышленник может заставить браузер отправить cookie (это и есть суть CSRF), но не может прочитать её и вставить в заголовок. Same-Origin Policy и HttpOnly мешают чтению.

Этот паттерн удобен для stateless API и микросервисов, но требует криптографически стойкого токена и защиты от подделки cookie.


Origin, Referer и дополнительные проверки

Сервер может проверять заголовки запроса:

  • Origin — домен, с которого пришёл запрос (отправляется при POST, PUT, DELETE).
  • Referer — полный URL предыдущей страницы.
export async function POST(req: Request) {
  const origin = req.headers.get('origin') || req.headers.get('referer');
  const allowed = ['https://mysite.com'];

  if (!origin || !allowed.some((o) => origin.startsWith(o))) {
    return Response.json({ error: 'Invalid origin' }, { status: 403 });
  }
}

Ограничения

  • Origin может отсутствовать в некоторых запросах.
  • Referer может быть заблокирован расширениями браузера или настройками приватности.
  • Эти проверки хороши как дополнительный слой, но не заменяют CSRF-токены.

CSRF в React, Vue, Next.js и Nuxt

React / Vue

Сами по себе фреймворки не защищают от CSRF — это задача серверной части. Но они влияют на то, как передаётся токен:

  • В классических формах — скрытое поле <input type="hidden" name="csrf_token" value={token} />.
  • В SPA — заголовок X-CSRF-Token или кастомный header.
  • React 19 <form action={serverAction}>: фреймворк может автоматически встраивать защиту, если Server Action настроен корректно, но лучше явно проверять Origin и использовать CSRF-токены для критичных операций.

Next.js

  • next/headers позволяет читать и устанавливать cookies в Server Components и Route Handlers.
  • Middleware может проверять CSRF-токены до выполнения запроса.
  • Для Server Actions важно валидировать входные данные, авторизацию и Origin.

Nuxt

  • nuxt-security модуль может настраивать CORS, CSP и заголовки.
  • Server routes в Nitro получают доступ к cookies через getCookie(event).
  • В Nuxt 3 для CSRF-защиты можно использовать h3 middleware или встроенные механизмы сессий.

Когда CSRF-защита не нужна

CSRF актуален только при cookie-based аутентификации. Если вы используете:

  • Bearer tokens в заголовке Authorization, которые фронтенд хранит в памяти;
  • JWT в Authorization: Bearer, не cookie;
  • API-ключи, передаваемые явно;

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

Но: хранение токенов в памяти или localStorage создаёт другие риски (XSS, утечка через скрипты). Выбор между cookies + CSRF и Bearer + XSS — это классический компромисс безопасности.


Практический чек-лист

Базовая защита

  • Установить SameSite=Lax или SameSite=Strict для всех сессионных cookies.
  • Использовать Secure (только HTTPS) и HttpOnly где возможно.
  • Добавить CSRF-токены для критичных операций: переводы, удаление, изменение email/пароля.

Для SPA

  • Получать CSRF-токен через отдельный endpoint или встраивать в HTML.
  • Отправлять токен в заголовке X-CSRF-Token или X-XSRF-Token.
  • Проверять Origin на сервере.

Для Next.js / Nuxt

  • Настроить cookies в middleware / server routes.
  • Валидировать Server Actions и API routes.
  • Использовать nuxt-security или аналогичные модули.

Антипаттерны

  • Не полагайтесь только на CORS — CORS не защищает от CSRF.
  • Не используйте GET-запросы для действий, изменяющих состояние.
  • Не доверяйте заголовку Referer в одиночку.

Терминология CSRF

ТерминЗначение
CSRFПодделка межсайтовых запросов от имени авторизованного пользователя
SameSiteАтрибут cookie, контролирующий cross-origin отправку
CSRF-токенСекретное значение, которое сервер проверяет при мутациях
Double Submit CookieПаттерн: токен одновременно в cookie и в заголовке/форме
OriginЗаголовок с доменом источника запроса
RefererЗаголовок с полным URL источника
HttpOnlyCookie, недоступная из JavaScript
Secure cookieCookie, передаваемая только по HTTPS
Preflight requestOPTIONS-запрос перед CORS-запросом
SOPSame-Origin Policy — политика одного источника

Ключевые тезисы для интервью

  • CSRF — атака на механизм аутентификации браузера: cookie автоматически прикрепляется к запросу, атакующий не видит ответ (SOP), но ему достаточно самого факта выполнения.
  • CSRF работает и с JSON API: credentials: 'include' в fetch, text/plain в форме или простой Content-Type обходят наивные защиты; CORS не защищает, так как контролирует чтение ответа, а не отправку.
  • SameSite=Lax (по умолчанию с 2020) блокирует cross-site POST, но не GET по ссылке; Strict строже, но неудобен для переходов из email.
  • CSRF-токены — золотой стандарт: сервер генерирует секрет, который атакующий не может прочитать из-за SOP; Double Submit Cookie — вариант для stateless API, где токен в cookie и заголовке сверяется сервером.
  • Origin/Referer — дополнительный слой, но не замена токенов: заголовки могут отсутствовать или подделываться прокси.
  • CSRF не нужен при Bearer-токенах в Authorization — браузер их не прикрепляет автоматически, но тогда токены живут в JS и уязвимы к XSS — фундаментальный компромисс.

Заключение

CSRF — атака не на сервер и не на фронтенд напрямую, а на механизм аутентификации браузера. Пока cookies привязаны к домену и отправляются автоматически, CSRF будет оставаться угрозой. Современная защита строится на трёх столпах: SameSite cookies блокируют большинство атак на уровне браузера, CSRF-токены защищают критичные операции, проверка Origin/Referer даёт дополнительный слой. Выбор между cookie-based auth + CSRF и Bearer + XSS-риск — фундаментальный компромисс, который нужно осознанно принимать при проектировании системы.

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