← Назад к вопросам
Что такое CSRF атака и как защититься в Express приложении?
2.0 Middle🔥 181 комментариев
#Node.js и JavaScript#Безопасность#Фреймворки и библиотеки
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
CSRF атаки и защита в Express
CSRF (Cross-Site Request Forgery) - это атака, при которой злоумышленник заставляет пользователя выполнить нежелательное действие на сайте, где он авторизован. Это одна из топ-10 уязвимостей по OWASP.
Как работает CSRF атака
Сценарий атаки
1. Пользователь авторизуется на bank.com
GET /login -> браузер сохраняет cookies с sessionId
2. Пользователь (не выходя) открывает악意ful.com
(например, по ссылке в письме)
3. На evil.com находится код:
<form action="https://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="1000">
</form>
<script>document.forms[0].submit();</script>
4. Браузер автоматически отправляет POST запрос на bank.com
вместе с cookies (потому что пользователь авторизован)
5. bank.com получает запрос и думает, что это легитимный запрос
от авторизованного пользователя (потому что есть cookies)
6. Деньги переводятся на счёт злоумышленника!
Почему это работает
Браузер автоматически отправляет cookies для запросов на тот же домен, где они были установлены. evil.com не может прочитать cookies (из-за Same-Origin Policy), но может заставить браузер отправить запрос с ними.
Защита 1: CSRF Tokens
Это основной способ защиты. Идея:
- Для каждой критической операции сервер генерирует уникальный токен
- Клиент должен отправить этот токен в запросе
- evil.com не может узнать токен (он не известен)
Использование csurf middleware
npm install csurf
const express = require("express");
const csrf = require("csurf");
const session = require("express-session");
const cookieParser = require("cookie-parser");
const app = express();
// 1. Парсирование cookies и session
app.use(cookieParser());
app.use(session({
secret: "your-secret-key",
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: true, // Только HTTPS
sameSite: "strict",
maxAge: 3600000 // 1 час
}
}));
// 2. Парсирование body
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
// 3. CSRF middleware (хранит токен в session)
const csrfProtection = csrf({ cookie: false }); // используем session, не cookie
// 4. GET запросы - возвращаем форму с CSRF токеном
app.get("/transfer", csrfProtection, (req, res) => {
res.send(`
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="${req.csrfToken()}">
<label>
To Account:
<input type="text" name="to" required>
</label>
<label>
Amount:
<input type="number" name="amount" required>
</label>
<button type="submit">Transfer</button>
</form>
`);
});
// 5. POST запросы - проверяем CSRF токен
app.post("/transfer", csrfProtection, (req, res) => {
const { to, amount } = req.body;
// Если токен неверный, csurf выбросит ошибку
console.log(`Transferring ${amount} to ${to}`);
res.json({ success: true, message: "Transfer completed" });
});
// 6. Обработчик ошибок CSRF
app.use((err, req, res, next) => {
if (err.code !== "EBADCSRFTOKEN") return next(err);
res.status(403).json({ error: "Invalid CSRF token" });
});
app.listen(3000);
CSRF Token для SPA (JSON API)
Для приложений, работающих с JSON (не HTML формы):
// Middleware для предоставления CSRF токена
app.get("/api/csrf-token", csrfProtection, (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
// POST с CSRF защитой
app.post("/api/transfer", csrfProtection, (req, res) => {
const { to, amount } = req.body;
console.log(`Transfer: ${amount} to ${to}`);
res.json({ success: true });
});
На фронтенде (React, Vue, Angular):
// 1. Получить токен при загрузке приложения
const getCsrfToken = async () => {
const response = await fetch("/api/csrf-token");
const { csrfToken } = await response.json();
return csrfToken;
};
// 2. Отправить запрос с токеном
const transfer = async (to, amount) => {
const csrfToken = await getCsrfToken();
const response = await fetch("/api/transfer", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": csrfToken // Отправляем токен в заголовке
},
body: JSON.stringify({ to, amount })
});
return response.json();
};
Защита 2: SameSite Cookie Attribute
Современный способ - флаг SameSite:
app.use(session({
secret: "secret",
cookie: {
sameSite: "strict", // Запретить отправку cookies для cross-site запросов
httpOnly: true,
secure: true
}
}));
Значения:
"strict"- cookies НЕ отправляются в cross-site запросах вообще"lax"- cookies отправляются только для GET запросов (навигация, img src)"none"- cookies отправляются всегда (требуетsecure: true)
Это защищает от большинства CSRF атак автоматически.
// Пример: evil.com больше не может отправить POST с cookies
<form action="https://bank.com/transfer" method="POST">
<!-- Браузер НЕ отправит sessionId cookies! -->
</form>
Защита 3: Origin и Referer проверка
const checkOrigin = (req, res, next) => {
const origin = req.get("origin");
const referer = req.get("referer");
const allowedOrigins = ["https://myapp.com"];
if (origin && !allowedOrigins.includes(origin)) {
return res.status(403).json({ error: "CSRF: Invalid origin" });
}
if (referer && !referer.startsWith("https://myapp.com")) {
return res.status(403).json({ error: "CSRF: Invalid referer" });
}
next();
};
app.post("/api/sensitive-action", checkOrigin, (req, res) => {
// Обработка запроса
res.json({ success: true });
});
Полный защищённый пример
const express = require("express");
const csrf = require("csurf");
const session = require("express-session");
const cookieParser = require("cookie-parser");
const helmet = require("helmet");
const app = express();
// Безопасность заголовков
app.use(helmet());
// Парсирование
app.use(cookieParser());
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
// Session
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 3600000
}
}));
// CSRF защита
const csrfProtection = csrf({ cookie: false });
// Проверка origin для POST/PUT/DELETE
const checkOrigin = (req, res, next) => {
if (["POST", "PUT", "DELETE", "PATCH"].includes(req.method)) {
const origin = req.get("origin");
if (origin && !origin.startsWith("https://myapp.com")) {
return res.status(403).json({ error: "CSRF blocked" });
}
}
next();
};
app.use(checkOrigin);
// Публичные эндпоинты
app.get("/", (req, res) => {
res.send("<h1>Banking App</h1>");
});
// API для получения CSRF токена
app.get("/api/csrf-token", csrfProtection, (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
// HTML форма с CSRF
app.get("/transfer", csrfProtection, (req, res) => {
res.send(`
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="${req.csrfToken()}">
<input type="text" name="to" placeholder="Account number">
<input type="number" name="amount" placeholder="Amount">
<button>Transfer</button>
</form>
`);
});
// POST с CSRF защитой
app.post("/transfer", csrfProtection, (req, res) => {
const { to, amount } = req.body;
// Обработка трансфера
res.json({ success: true, transferred: amount, to });
});
// Обработчик CSRF ошибок
app.use((err, req, res, next) => {
if (err.code === "EBADCSRFTOKEN") {
return res.status(403).json({ error: "CSRF token validation failed" });
}
next(err);
});
app.listen(3000, () => {
console.log("Bank app running on https://localhost:3000");
});
Тестирование CSRF защиты
# 1. Попытка без токена (должна не пройти)
curl -X POST http://localhost:3000/transfer \
-H "Content-Type: application/json" \
-d '{"to": "attacker", "amount": 1000}'
# Ответ: 403 Forbidden
# 2. Попытка с неверным токеном
curl -X POST http://localhost:3000/transfer \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: invalid-token" \
-d '{"to": "attacker", "amount": 1000}'
# Ответ: 403 Forbidden
Best Practices
- Всегда используй CSRF защиту для POST/PUT/DELETE операций
- SameSite cookies - это first line of defense в современных браузерах
- CSRF токены - для дополнительной защиты и старых браузеров
- Origin/Referer проверка - для defence in depth
- HTTPS обязателен - CSRF защита требует secure cookies
- Ротируй токены - генерируй новый после каждого использования (опционально)
- Краткосрочные токены - токены должны иметь срок действия
CSRF атаки опасны потому что используют доверие между браузером и сервером. Используй многоуровневую защиту.