CSRF (Cross-Site Request Forgery) возникает, когда браузер автоматически отправляет аутентификационные данные пользователя (cookies, HTTP-auth, session identifiers) на сторонний сайт без явного намерения пользователя. В результате злоумышленник может инициировать запросы от имени жертвы к доверенному сервису.
Ключевая особенность атаки заключается не в компрометации пароля, а в использовании уже установленной сессии. Если приложение полагается только на cookie для авторизации, любой POST-запрос, инициированный сторонним сайтом, может быть воспринят как легитимный.
Типичные последствия:
CSRF особенно опасен в приложениях без строгой проверки происхождения запроса.
Классические подходы включают:
SameSite cookies
SameSite=Lax или Strict ограничивает
отправку cookies в cross-site контекстеCSRF-токены
Проблема классической реализации токенов — безопасное хранение и защита от подмены на клиенте и сервере.
Библиотека Iron (hapi/iron) используется для криптографического запечатывания (sealing) данных, обеспечивая их целостность и конфиденциальность.
Основные свойства:
Это делает Iron удобным инструментом для хранения CSRF-токенов в cookie или защищённых payload-структурах.
Типовая схема:
Сервер генерирует CSRF-токен
Токен упаковывается через Iron (seal)
Упакованное значение сохраняется в cookie
При каждом запросе:
CSRF-токен должен быть криптографически стойким:
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
);
}
После упаковки значение отправляется в браузер:
HttpOnlySecure в HTTPSSameSite=Lax или Strictres.cookie('csrf_token', sealedToken, {
httpOnly: true,
secure: true,
sameSite: 'Lax'
});
Ключевой момент: клиент не должен иметь возможность модифицировать содержимое.
Наиболее распространённый подход:
Пример заголовка:
X-CSRF-Token: <token>
Процесс валидации:
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();
}
CSRF-проверка обычно вставляется после парсинга cookies и перед бизнес-логикой:
Порядок критичен: токен должен быть доступен до обработки запроса.
Для повышения безопасности применяется ротация:
Дополнительно можно вводить:
Пример расширенного payload:
{
csrf: token,
user: userId,
exp: Date.now() + 3600000
}
Iron не управляет временем напрямую, но sealed объект может содержать TTL:
exp после unsealCSRF-защита становится значительно надёжнее при добавлении контекстных проверок:
Эти механизмы не заменяют CSRF-токен, но усиливают модель защиты.
Неправильные подходы приводят к ослаблению защиты:
При масштабировании:
passwordВ условиях микросервисов:
Современные браузеры усиливают защиту:
SameSite=Strict практически устраняет CSRF в
большинстве сценариевFetch metadata headers (Sec-Fetch-Site) позволяют
фильтровать cross-site запросыОднако reliance only on browser behavior считается недостаточным, поэтому CSRF-токены остаются обязательным слоем защиты в stateful приложениях.
Итоговая архитектура защиты обычно включает:
Такая комбинация обеспечивает устойчивость к классическим CSRF-атакам даже при частичных обходах отдельных защитных механизмов.