Что такое CSRF-атака и как от неё защититься?

Ответ

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

Сценарий атаки:

  1. Пользователь авторизуется на сайте bank.com. Его сессия сохраняется в cookie.
  2. Пользователь переходит на вредоносный сайт, который содержит скрытую форму или отправляет AJAX-запрос на bank.com/transfer?to=attacker&amount=1000.
  3. Браузер автоматически прикрепляет cookies сессии к этому запросу.
  4. Сервер bank.com видит валидную сессию и выполняет перевод.

Основной метод защиты — CSRF-токены: Сервер генерирует уникальный, секретный токен для каждой пользовательской сессии и внедряет его в формы (как скрытое поле) или в заголовки AJAX-запросов. При получении запроса на изменение состояния сервер проверяет соответствие токена из запроса токену в сессии.

Пример реализации защиты на PHP:

// 1. Генерация и сохранение токена в сессии при её старте
session_start();
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// 2. Внедрение токена в HTML-форму
<form action="/transfer.php" method="POST">
    <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES); ?>">
    <!-- остальные поля формы -->
</form>

// 3. Проверка токена при обработке POST-запроса
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
        http_response_code(403);
        die('Invalid CSRF token.');
    }
    // Токен верный, обрабатываем запрос
}

Важные моменты:

  • Используйте криптографически безопасные генераторы (random_bytes()).
  • Проверяйте с помощью hash_equals() для защиты от атак по времени.
  • В современных фреймворках (Laravel, Symfony, Django, Spring Security) защита от CSRF встроена и включается по умолчанию.

Ответ 18+ 🔞

Давай разжую эту тему, как будто мы на кухне сидим и я тебе объясняю, почему твой сайт могут обчистить, пока ты в туалете сидишь.

Представь себе такую картину. Ты залогинился в свой интернет-банк, сессия живая, кукисы в браузере тусуются. Всё спокойно. А потом ты, такой довольный, переходишь по ссылке на какой-нибудь форум, где тебе картинку с котиком обещали. А там, ёпта, не котик, а скрытая форма, которая в тихую, пока ты не видишь, шлёт запрос твоему же банку: «Перевести все деньги на счёт злоумышленника». И браузер твой, такой преданный пёс, автоматом цепляет к этому запросу твои же куки с сессией. Банк видит: «А, это наш Вася! Сессия валидная. Переводим!» И всё, приехали. Это и есть CSRF — подделка межсайтовых запросов. Овердохуища рисков, правда?

Короче, злодей может заставить твой браузер сделать что-то на доверенном сайте без твоего ведома. Волнение ебать просто, когда осознаёшь.

Что делать? Основной щит — это CSRF-токены. Суть проста, как три копейки: сервер выдаёт для каждой сессии свой секретный ключ-пароль (токен) и прячет его в форму. Когда форма отправляется назад, сервер сверяет: «А тот ли это самый Вася?». Если токен не совпал или его нет — запрос посылается на три весёлые буквы.

Смотри, как это в коде выглядит, на примере голого PHP. Подозрение ебать чувствую, что многие до сих пор так и пишут.

// 1. Запускаем сессию и генерируем токен, если его ещё нет.
// Без этого — вообще никуда.
session_start();
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // Криптографически безопасно, не костыли!
}

// 2. Встраиваем этот токен в каждую форму, как скрытое поле.
// Если этого не сделать — всё, манда с ушами, защита не работает.
<form action="/transfer.php" method="POST">
    <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES); ?>">
    <!-- А тут уже твои обычные поля: сумма, счёт и т.д. -->
</form>

// 3. Когда форма прилетела, ПЕРВЫМ ДЕЛОМ проверяем токен.
// Не после того как деньги уже перевели, а сразу!
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
        http_response_code(403); // Отказано!
        die('Invalid CSRF token. Иди отсюда.');
    }
    // Только если токен сошёлся — делаем опасные операции (перевод, смену пароля).
}

Важные моменты, на которых все обламываются:

  • random_bytes() — это твой друг. Не rand(), не mt_rand(), а именно он. Иначе токен угадают, и будет вам хиросима и нигерсраки.
  • hash_equals() — для сравнения. Она от атак по времени защищает, чтобы по скорости ответа не догадались, где ошибка.
  • В современных фреймворках типа Laravel, Django или Spring Security эта защита уже встроена и включена по умолчанию. Но это не значит, что её нельзя случайно впендюрить себе в проект, отключив одной галочкой. Доверия ебать ноль к настройкам по умолчанию — всегда проверяй!

Запомни главное: если на твоём сайте есть действие, которое меняет состояние (перевод денег, пост в блог, смена почты), и оно работает по авторизованной сессии — ты ходишь по охуенно тонкому льду без CSRF-токена. Любой школьник с полчаса времени может написать страничку, которая обчистит твоих пользователей. Не будь распиздяем, ставь защиту.