Сергей Попов
Администратор
- 30.12.2015
- 4 804
- 6 514
Всем привет! Представьте на секунду: вы зашли на форум, кликнули на безобидную с виду картинку с котиком, а в этот момент с вашего банковского счета списались деньги. Или, что менее драматично, но не менее неприятно, изменился email в вашем профиле на рабочем портале. Фантастика? Нет, это реальный сценарий атаки, известной как CSRF-уязвимость.
Введение В мире веб-безопасности есть уязвимости громкие, как Log4Shell, а есть тихие и коварные. CSRF, или межсайтовая подделка запросов (Cross-Site Request Forgery), — как раз из второй категории. Она не крадет данные напрямую, но заставляет браузер пользователя выполнять действия от его имени без его ведома. И хотя современные фреймворки и браузеры сделали нашу жизнь проще, эта старушка все еще находит лазейки в защите. Почему? Потому что она бьет не по технике, а по логике доверия между пользователем, его браузером и сайтом. Давайте разберемся, как эта магия работает, где искать уязвимость и, самое главное, как построить надежную защиту от CSRF.
Что такое CSRF? Всё проще, чем кажется
Если объяснять на пальцах, CSRF-уязвимость — это как если бы вы дали кому-то подписанный, но незаполненный чек. Вы доверяете своему банку и заранее ставите подпись. Но если этот чек попадет в руки мошенника, он сможет вписать туда любую сумму и имя получателя, а банк его без проблем примет, ведь подпись-то ваша.В веб-мире ваш "подписанный чек" — это сессионная cookie, которую браузер сохраняет после того, как вы залогинились на каком-либо сайте (например, в интернет-магазине). Эта cookie автоматически прикрепляется ко всем запросам на этот сайт, подтверждая, что это "вы". Злоумышленник не может украсть или прочитать эту cookie, но он может заставить ваш браузер отправить запрос на сайт магазина, и браузер послушно приложит к нему вашу "подпись". А магазин, увидев валидную сессию, выполнит команду.
Механизм атаки: Как злоумышленник дергает за ниточки
Стоит понимать, что атака редко происходит в вакууме. Зачастую ей предшествует этап разведки, когда злоумышленник ищет подходящую цель. Например, с помощью инструментов для анализа открытых данных (OSINT) он может идентифицировать сотрудников с высокими привилегиями в системе. О том, насколько доступными стали Топ-11 OSINT-ботов для новичков, можно судить по недавним материалам коллег. И уже после такой подготовки начинается техническая часть.Давайте разложим типичную CSRF-атаку по шагам. Это поможет понять, где именно "тонко" и где потом "рвется".
- Жертва входит в систему. Алиса логинится на сайте своего банка
my-cool-bank.com
. Банк устанавливает ей в браузер сессионную cookie. - Жертва посещает вредоносный сайт. Через некоторое время Алиса открывает в новой вкладке сайт
evil-cats.com
, который ей прислал знакомый. - Скрытый запрос. На этом сайте в коде спрятано что-то вроде этого:
HTML:<img src="http://my-cool-bank.com/transfer?to=attacker&amount=10000" width="1" height="1">
- Браузер "помогает". Как только браузер Алисы пытается загрузить эту "картинку", он отправляет GET-запрос на
my-cool-bank.com
. И, будучи услужливым, он прикрепляет к этому запросу все cookie, связанные с доменомmy-cool-bank.com
. Включая ту самую, сессионную. - Сервер выполняет команду. Сервер банка получает запрос на перевод 10 000 на счет
attacker
. Он проверяет cookie, видит, что сессия принадлежит Алисе, и решает, что это она сама инициировала перевод. Транзакция выполнена.
Примеры из практики: от теории к реальным кейсам
Классика жанра: Атака через GET-запрос
Пример выше с тегом<img>
— это хрестоматийный пример CSRF-атаки. Он работает, потому что GET-запросы могут быть вызваны множеством способов: вставка картинки, CSS-файла, скрипта.Главный урок здесь: никогда, слышите, НИКОГДА не используйте GET-запросы для операций, которые изменяют состояние данных (создание, изменение, удаление). Перевод денег, смена пароля, добавление товара в корзину — все это должно работать только через POST, PUT или DELETE запросы.
Современный подход: Атака через POST-запрос
Не совсем. Злоумышленник может создать на своем сайте форму, которая будет отправлять POST-запрос на ваш сервер, и заставить ее отправиться автоматически с помощью JavaScript."Хорошо, скажете вы: мы используем POST для всех важных действий. Теперь мы в безопасности?"
Представьте, на сайте
evil-hacker.net
есть такой код:
HTML:
<body onload="document.forms[0].submit()">
<form action="https://your-social-network.com/change-email" method="POST">
<input type="hidden" name="email" value="hacker@evil.com" />
</form>
</body>
your-social-network.com
, откроет эту страницу, JavaScript без ее ведома отправит форму. Браузер снова приложит cookie, и email в профиле пользователя будет изменен на email хакера.Лучший способ по-настоящему понять такую атаку — это воспроизвести ее своими руками. Разумеется, в безопасной и контролируемой среде. Если вы задумываетесь о создании собственной лаборатории для практики, но не знаете, с чего начать и как оставаться в рамках закона, на Codeby недавно вышло отличное руководство про Этичный хакинг: обучение кибербезопасности по закону РФ
Главный вопрос: Как защититься от CSRF-атак?
К счастью, защититься от этого безобразия можно, и даже нужно. Лучше всего работает многоуровневая защита.Золотой стандарт: CSRF-токен (Synchronizer Token Pattern)
Это самый надежный и распространенный метод. Его суть проста:- Когда пользователь заходит на страницу с формой (например, смена пароля), сервер генерирует уникальный, случайный и непредсказуемый токен (CSRF-токен).
- Этот токен встраивается в форму в виде скрытого поля.
- Токен также сохраняется на сервере, привязанный к сессии пользователя.
- Когда пользователь отправляет форму, сервер сравнивает токен из формы с токеном, сохраненным в сессии.
- Если они совпадают — запрос считается легитимным. Если не совпадают или токен отсутствует — запрос отклоняется, так как, скорее всего, он пришел со стороннего сайта, который не мог знать правильный токен.
Python:
from flask import Flask, render_template, request, session, abort
import os
app = Flask(__name__)
# Важно установить секретный ключ для управления сессиями
app.config['SECRET_KEY'] = os.urandom(24)
@app.route('/settings', methods=['GET', 'POST'])
def settings():
if request.method == 'POST':
# 3 & 4. Проверяем токен при отправке формы
if not session.get('csrf_token') or session['csrf_token'] != request.form.get('csrf_token'):
abort(403) # Отклоняем запрос, если токен неверный
# ... логика изменения настроек ...
return "Настройки сохранены!"
# 1 & 2. Генерируем и передаем токен в шаблон при отображении страницы
session['csrf_token'] = os.urandom(24).hex()
return render_template('settings.html', csrf_token=session['csrf_token'])
# В шаблоне settings.html:
# <form method="post">
# <input type="hidden" name="csrf_token" value="{{ csrf_token }}">
# ... другие поля формы ...
# <button type="submit">Сохранить</button>
# </form>
SameSite Cookies: Встроенная защита браузера
Это относительно новый механизм, который указывает браузеру, когда следует отправлять cookie. У атрибутаSameSite
есть три значения:Strict
: Cookie будут отправлены только в том случае, если запрос идет с того же сайта. Самый надежный вариант, но может "сломать" некоторые сценарии, например, переход на сайт по ссылке из письма.Lax
: Cookie отправляются при навигации на ваш сайт с других сайтов (например, по ссылке), но не отправляются при фоновых запросах (загрузка картинок, iframe, POST-формы со сторонних сайтов). Сегодня это значение по умолчанию во многих браузерах. Это уже серьезная защита!None
: Cookie отправляются всегда. Используется для межсайтовых сценариев, но требует обязательного флагаSecure
(то есть, сайт должен работать по HTTPS).
SameSite=Lax
или SameSite=Strict
для сессионных cookie — отличный дополнительный рубеж обороны.Проверка заголовков Origin
и Referer
При межсайтовом запросе браузер обычно добавляет заголовок Origin
, указывающий на домен-инициатор запроса. Если его нет, он может добавить Referer
(URL страницы-источника).Вы можете на сервере проверять эти заголовки. Если запрос на изменение данных приходит с
Origin: https://evil-cats.com
, его нужно блокировать. Этот метод прост, но не на 100% надежен: некоторые прокси или настройки приватности могут вырезать эти заголовки. Поэтому его стоит использовать как дополнительную меру, а не как основную.Чек-лист для самопроверки
Проверка на наличие CSRF-уязвимостей — это обязательный пункт в любом аудите безопасности или полноценном пентесте. Кстати, если вы только начинаете свой путь в этой области, можете заглянуть в Пентест с нуля: пошаговое руководство для новичков, чтобы составить полную картину процесса.Не используете GET-запросы для изменения состояния (create, update, delete).
Внедрили CSRF-токены для всех форм и AJAX-запросов, изменяющих данные.
Используете атрибут
SameSite
(Lax
илиStrict
) для сессионных cookie.В качестве дополнительной меры проверяете заголовки
Origin
/Referer
.Для критически важных операций (смена пароля, финансовые транзакции) требуете повторного ввода пароля.
Заключение
Межсайтовая подделка запросов — это классическая уязвимость, основанная на злоупотреблении доверием. Хотя современные браузеры и фреймворки сильно усложнили жизнь атакующим, расслабляться нельзя. Понимание механизма атаки и внедрение многоуровневой защиты, где CSRF-токен является ядром, аSameSite
cookie и проверка заголовков — надежной броней, — это признак хорошего тона для любого разработчика и тестировщика. Не доверяйте браузеру пользователя делать вашу работу по безопасности. Проверяйте всё на сервере.FAQ
- Вопрос 1: CSRF-токен должен быть разным для каждой сессии или для каждого запроса?
Ответ: Лучшая практика — генерировать новый токен для каждой формы или state-changing операции (per-request token). Это усложняет атаку, если на сайте есть другие уязвимости (например, XSS). Однако, использование одного токена на всю сессию (per-session token) также является приемлемым и более простым в реализации вариантом. - Вопрос 2:
SameSite=Lax
полностью защищает от CSRF?
Ответ: Нет, не полностью.Lax
защищает от CSRF-атак, инициированных через POST-запросы со сторонних сайтов, iframe, картинки. Однако, если атака использует GET-запрос и пользователь просто переходит по ссылке (<a href="...">
), cookie будут отправлены. ПоэтомуSameSite
— это отличный дополнительный слой, но не замена CSRF-токенам. - Вопрос 3: Уязвимы ли REST API к CSRF?
Ответ: Зависит от механизма аутентификации. Если ваше API использует для аутентификации сессионные cookie (stateful), то оно уязвимо точно так же, как и обычный сайт. Если же API использует stateless-аутентификацию, например, через передачу токена (JWT) в заголовкеAuthorization
, то оно не уязвимо к CSRF, потому что браузер не прикрепляет этот заголовок к запросам автоматически.