Защита от подделки запроса (CSRF)

CSRF (Cross-Site Request Forgery) возникает, когда браузер автоматически отправляет аутентификационные данные пользователя (cookies, HTTP-auth, session identifiers) на сторонний сайт без явного намерения пользователя. В результате злоумышленник может инициировать запросы от имени жертвы к доверенному сервису.

Ключевая особенность атаки заключается не в компрометации пароля, а в использовании уже установленной сессии. Если приложение полагается только на cookie для авторизации, любой POST-запрос, инициированный сторонним сайтом, может быть воспринят как легитимный.

Типичные последствия:

  • несанкционированное изменение данных пользователя
  • выполнение финансовых операций
  • изменение настроек аккаунта
  • привязка внешних сервисов

CSRF особенно опасен в приложениях без строгой проверки происхождения запроса.


Базовые механизмы защиты и их ограничения

Классические подходы включают:

SameSite cookies

  • SameSite=Lax или Strict ограничивает отправку cookies в cross-site контексте
  • снижает риск CSRF, но не закрывает все сценарии (например, сложные редиректы или старые браузеры)

CSRF-токены

  • сервер генерирует уникальный токен
  • токен включается в форму или заголовок
  • сервер сверяет значение при каждом изменяющемся запросе

Проблема классической реализации токенов — безопасное хранение и защита от подмены на клиенте и сервере.


Роль библиотеки Iron в защите CSRF

Библиотека Iron (hapi/iron) используется для криптографического запечатывания (sealing) данных, обеспечивая их целостность и конфиденциальность.

Основные свойства:

  • данные невозможно изменить без обнаружения подделки
  • используется симметричное шифрование с секретным ключом
  • поддерживается сериализация сложных объектов
  • встроенная защита от tampering

Это делает Iron удобным инструментом для хранения CSRF-токенов в cookie или защищённых payload-структурах.


Архитектура CSRF-защиты с использованием Iron

Типовая схема:

  1. Сервер генерирует CSRF-токен

  2. Токен упаковывается через Iron (seal)

  3. Упакованное значение сохраняется в cookie

  4. При каждом запросе:

    • клиент отправляет токен в заголовке или body
    • сервер извлекает cookie
    • выполняется unseal через Iron
    • токены сравниваются

Генерация и упаковка токена

CSRF-токен должен быть криптографически стойким:

  • 32–64 байта случайных данных
  • генерация через crypto.randomBytes

Пример логики:

import Iron from '@hapi/iron';
import crypto from 'crypto';

const password = process.env.IRON_SECRET;

function generateCsrfToken() {
  return crypto.randomBytes(32).toString('hex');
}

async function sealCsrf(token) {
  return await Iron.seal(
    { csrf: token },
    password,
    Iron.defaults
  );
}

После упаковки значение отправляется в браузер:

  • cookie помечается как HttpOnly
  • используется Secure в HTTPS
  • применяется SameSite=Lax или Strict
res.cookie('csrf_token', sealedToken, {
  httpOnly: true,
  secure: true,
  sameSite: 'Lax'
});

Ключевой момент: клиент не должен иметь возможность модифицировать содержимое.


Двойная отправка токена (Double Submit Pattern)

Наиболее распространённый подход:

  • токен хранится в cookie (sealed через Iron)
  • клиент также отправляет токен в заголовке или теле запроса

Пример заголовка:

X-CSRF-Token: <token>

Проверка токена на сервере

Процесс валидации:

  1. извлечь sealed cookie
  2. выполнить unseal через Iron
  3. сравнить с токеном из запроса
async function verifyCsrf(req, res, next) {
  const sealed = req.cookies.csrf_token;
  const headerToken = req.headers['x-csrf-token'];

  if (!sealed || !headerToken) {
    return res.status(403).send('CSRF missing');
  }

  let data;

  try {
    data = await Iron.unseal(sealed, password, Iron.defaults);
  } catch (e) {
    return res.status(403).send('Invalid CSRF seal');
  }

  if (data.csrf !== headerToken) {
    return res.status(403).send('CSRF mismatch');
  }

  next();
}

Интеграция с middleware (Express-подобная архитектура)

CSRF-проверка обычно вставляется после парсинга cookies и перед бизнес-логикой:

  • cookie-parser
  • auth middleware
  • csrf middleware
  • route handlers

Порядок критичен: токен должен быть доступен до обработки запроса.


Ротация CSRF-токенов

Для повышения безопасности применяется ротация:

  • при каждом логине создаётся новый токен
  • при обновлении сессии токен может пересоздаваться
  • старые sealed значения становятся недействительными

Дополнительно можно вводить:

  • TTL внутри sealed объекта
  • привязку к userId или sessionId

Пример расширенного payload:

{
  csrf: token,
  user: userId,
  exp: Date.now() + 3600000
}

Связка Iron и временных ограничений

Iron не управляет временем напрямую, но sealed объект может содержать TTL:

  • проверка exp после unseal
  • отклонение просроченных токенов
  • защита от replay-атак

Усиление защиты через контекст запроса

CSRF-защита становится значительно надёжнее при добавлении контекстных проверок:

  • сравнение Origin header
  • проверка Referer
  • привязка токена к IP (с осторожностью)
  • привязка к User-Agent (как дополнительный сигнал)

Эти механизмы не заменяют CSRF-токен, но усиливают модель защиты.


Типичные ошибки при реализации

Неправильные подходы приводят к ослаблению защиты:

  • хранение CSRF в localStorage без дополнительных проверок
  • отсутствие проверки заголовка Origin
  • использование предсказуемых токенов
  • отсутствие защиты unseal-операции (необработанные ошибки Iron)
  • отсутствие привязки токена к сессии пользователя

Особенности работы Iron в распределённых системах

При масштабировании:

  • все инстансы должны использовать одинаковый password
  • несогласованность ключей приводит к невозможности unseal
  • рекомендуется централизованное управление секретами

В условиях микросервисов:

  • CSRF-валидация может выполняться на gateway-уровне
  • sealed токен остаётся валидным между сервисами

Комбинация CSRF с современными политиками браузеров

Современные браузеры усиливают защиту:

  • SameSite=Strict практически устраняет CSRF в большинстве сценариев
  • Fetch metadata headers (Sec-Fetch-Site) позволяют фильтровать cross-site запросы

Однако reliance only on browser behavior считается недостаточным, поэтому CSRF-токены остаются обязательным слоем защиты в stateful приложениях.


Практическая модель безопасности с Iron

Итоговая архитектура защиты обычно включает:

  • Iron sealed CSRF token в HttpOnly cookie
  • double submit token через header
  • проверка Origin/Referer
  • SameSite cookies
  • TTL внутри sealed payload
  • привязка к сессии пользователя

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