На пентесте финтех-платформы разработчики клялись, что CSRF атака невозможна - "у нас настроен CORS". Через 40 минут был готов рабочий PoC: автоматически отправляемая POST-форма меняла email привязки аккаунта. CORS к этому запросу не имел никакого отношения - браузер честно отправлял сессионную cookie вместе с "простым" запросом, а сервер не проверял ничего, кроме неё. Типичная история с Cross-Site Request Forgery: уязвимость считают закрытой, хотя на деле она замаскирована неверным пониманием того, как работает браузер.
Как работает CSRF атака - механика для пентестера
Межсайтовая подделка запроса (CSRF, она же XSRF, "sea-surf", session riding) - атака, при которой браузер жертвы выполняет HTTP-запрос к целевому веб-приложению от имени аутентифицированного пользователя. Жертва не вводит данные, не нажимает "Подтвердить" - браузер делает это сам, потому что прикрепляет cookies к каждому запросу на соответствующий домен. Подробнее - в нашем подробном разборе пентест веб-приложений.Согласно описанию PortSwigger, для CSRF атаки нужны три условия:
- Целевое действие - в приложении есть функция, которую атакующий хочет вызвать: смена пароля, перевод средств, изменение email, добавление администратора.
- Сессия на cookies - приложение идентифицирует пользователя исключительно по сессионной cookie без дополнительных проверок.
- Предсказуемые параметры - все параметры запроса известны атакующему заранее. Если для смены пароля требуется ввод текущего пароля - CSRF не сработает.
CSRF не позволяет прочитать ответ сервера (ограничение Same-Origin Policy). Атака работает только для действий с побочными эффектами: изменение данных, отправка форм, выполнение транзакций. Для кражи данных потребуется связка CSRF + XSS - и об этом прямо предупреждает OWASP: Cross-Site Scripting может обойти любые механизмы защиты от CSRF.
Место CSRF в цепочке атаки
По MITRE ATT&CK, CSRF задействует несколько техник:- Malicious Link (T1204.001, Execution) - жертву нужно заставить перейти по ссылке или открыть страницу с эксплойтом.
- Browser Session Hijacking (T1185, Collection) - атакующий эксплуатирует активную браузерную сессию жертвы для выполнения действий.
- Exploit Public-Facing Application (T1190, Initial Access) - CSRF используется как первый шаг для закрепления, например через смену email с последующим сбросом пароля.
[Применимо: внешний пентест, любые веб-приложения с cookie-based аутентификацией. Во внутреннем пентесте - CSRF на административные панели (роутеры, принтеры, CI/CD), доступные через localhost]
CSRF пример атаки: от GET до POST
CVE-2008-6586 - классика через GET-запрос
Реальная CSRF уязвимость CVE-2008-6586 (CWE-352) в μTorrent WebUI 0.315 - наглядная демонстрация того, как НЕ надо проектировать API. Веб-интерфейс μTorrent наlocalhost:8080 выполнял критические операции через GET без верификации: принудительная загрузка произвольного торрент-файла и смена пароля администратора.Эксплойт EDB-31672 (автор th3.r00k, опубликован 18 апреля 2008) размещался на форумах и в email как невидимая картинка:
HTML:
<img src="http://localhost:8080/gui/?action=setsetting&s=webui.password&v=eviladmin" width="0" height="0">
Построение PoC для POST-запросов
Большинство современных приложений используют POST для изменения данных. Это усложняет эксплуатацию на один шаг - не более. Согласно описанию PortSwigger, стандартный CSRF PoC для POST выглядит так:
HTML:
<form action="https://target.com/email/change" method="POST">
<input type="hidden" name="email" value="attacker@evil.com"/>
</form>
<script>document.forms[0].submit();</script>
В Burp Suite Professional это автоматизировано: правый клик на запросе -> Engagement tools -> Generate CSRF PoC. Генератор создаёт HTML с формой, учитывая все параметры. На практике я начинаю с генератора, а потом дорабатываю PoC руками: добавляю iframe для скрытия перенаправления, обрабатываю нестандартный Content-Type или подменяю метод.
Ограничения CSRF атаки
CSRF - не универсальный вектор. Вот когда атака не сработает:| Условие | Почему CSRF не работает |
|---|---|
| SameSite=Strict cookies | Браузер не отправит cookie при cross-site запросе |
| Требуется текущий пароль | Атакующий не знает значение |
| CSRF токен привязан к сессии | Атакующий не может предсказать токен |
| API принимает только JSON (Content-Type: application/json) | HTML-форма не отправит такой Content-Type без preflight |
| Кастомные заголовки (X-Requested-With) | Браузер не добавит кастомный заголовок при cross-site запросе без CORS-разрешения |
Обход защиты от CSRF: где ломаются механизмы
📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме
Этот приём особенно актуален с учётом SameSite cookie. С 2021 года Chrome по умолчанию ставит
SameSite=Lax для cookies без явного атрибута. Lax блокирует cross-site POST-запросы, но пропускает GET-запросы при навигации верхнего уровня (клик по ссылке). Если приложение принимает state-changing GET - SameSite=Lax не защищает. На пентестах я регулярно вижу эту комбинацию: разработчик полагается на дефолтный Lax и не ставит CSRF токен, а потом обнаруживается GET-эндпоинт с побочным эффектом.Client-side CSRF и Login CSRF
Согласно OWASP Prevention Cheat Sheet, есть отдельный класс CSRF уязвимостей на стороне клиента. JavaScript на странице приложения берёт значение из URL-параметра и использует его для формирования AJAX-запроса. Атакующий конструирует ссылку, в которой контролируемый параметр направляет запрос на нужный эндпоинт - всё происходит внутри origin, стандартные проверки (включая SameSite и Referer) слепы.Отдельная категория - Login CSRF. Атакующий подделывает запрос на логин, чтобы жертва авторизовалась под чужой учёткой. После этого жертва выполняет действия (сохраняет документы, вводит данные карты), а атакующий видит всё через свой аккаунт. Login CSRF часто игнорируется при тестировании, хотя позволяет собирать конфиденциальную информацию без единого алерта. Я проверяю Login CSRF на каждом проекте - и раз в несколько месяцев нахожу.
Как защитить веб-приложение от CSRF
Согласно OWASP, защита от CSRF строится на комбинации нескольких уровней.
CSRF токен - Synchronizer Token Pattern
Самый надёжный метод. Сервер генерирует уникальный, непредсказуемый CSRF токен для каждой сессии и вставляет его в форму как скрытое поле. При отправке сервер сравнивает полученный токен с сохранённым.Требования к токену по OWASP: уникальность для сессии, генерация через CSPRNG, запрет на передачу в URL (утечка через Referer, логи, историю браузера), запрет на хранение в cookie для синхронизированного паттерна.
Для SPA-приложений токен передаётся через мета-тег в HTML и прикрепляется к AJAX-запросам через кастомный заголовок
X-CSRF-Token. Кастомный заголовок автоматически попадает под Same-Origin Policy - дополнительный слой защиты без лишнего кода.Подписанный Double-Submit Cookie
Для stateless-архитектур OWASP рекомендует Signed Double-Submit Cookie. Токен помещается и в cookie, и в параметр формы. Сервер сравнивает оба значения. Критично: токен обязательно привязывается к session ID через HMAC с серверным секретом. Наивный Double-Submit без подписи - OWASP явно помечает как DISCOURAGED - уязвим к cookie injection через поддомен или CRLF.SameSite Cookie и Fetch Metadata
АтрибутSameSite контролирует отправку cookies при cross-site запросах:| Значение | Поведение | Уровень защиты |
|---|---|---|
| Strict | Cookie не отправляется при любых cross-site запросах | Полный, но ломает UX (ссылки из email без авторизации) |
| Lax | Cookie отправляется при навигации верхнего уровня (GET) | Частичный - не покрывает GET-based CSRF |
| None | Cookie отправляется всегда (требуется Secure) | Отсутствует |
Fetch Metadata headers - более свежий подход: браузеры отправляют
Sec-Fetch-Site(Sec-Fetch-Site), Sec-Fetch-Mode, Sec-Fetch-Dest с каждым запросом. Сервер отклоняет запросы с Sec-Fetch-Site отличным от same-origin. Go включил поддержку этого механизма в стандартную библиотеку начиная с версии 1.25 (CrossOriginProtection). Плюс - не требует модификации фронтенда.Когда хватает встроенной защиты фреймворка
Перед реализацией собственного механизма - проверьте фреймворк. Django, Laravel, .NET, Spring Security дают готовые решения. Собственная реализация - источник ошибок. Показательный пример описан разработчиками Банки.ру на Хабре: библиотека csurf для Express.js, созданная для защиты от CSRF, сама содержала CSRF уязвимость и была архивирована. Команде пришлось писать защиту с нуля - с HMAC-подписью, солёным хешированием и хранением в cookie по паттерну Double Submit. Ирония - библиотека "защиты" оказалась дырявее, чем отсутствие защиты вовсе.Тестирование веб-приложения на CSRF уязвимость
Чеклист, пригодный для передачи в отчёт по пентесту:- Определите state-changing эндпоинты - всё, что меняет данные: POST, PUT, DELETE, а также GET с побочными эффектами. Приоритет: смена email, пароля, прав доступа, финансовые операции.
- Проверьте наличие CSRF токена - ищите скрытое поле или кастомный заголовок в запросе. Нет - конструируйте PoC.
- Удалите токен из запроса - в Burp Repeater уберите параметр с токеном. Сервер принял запрос? Защита формальна.
- Подставьте чужой токен - зарегистрируйте второй аккаунт, возьмите его токен. Если работает - токен не привязан к сессии.
- Смените HTTP-метод - замените POST на GET, перенесите параметры в URL. Проверьте, применяется ли валидация.
- Проверьте атрибуты session cookie -
SameSite=Noneили отсутствие атрибута в не-Chromium браузерах расширяет вектор. - Подавите Referer - если защита строится на проверке Referer, добавьте в PoC:
<meta name="referrer" content="no-referrer">. - Проверьте Login CSRF - подделайте запрос на вход под учёткой атакующего.
- Постройте и подтвердите PoC - Burp Suite -> Generate CSRF PoC -> откройте в браузере с авторизованной сессией -> подтвердите выполнение действия.
- Документируйте полную цепочку импакта - в отчёте: "смена email -> сброс пароля -> полный захват аккаунта", не абстрактное "обнаружена CSRF уязвимость".
Половина пентест-отчётов с пометкой "CSRF - Low/Informational" занижает реальный импакт. Разработчики слышат "CSRF" и думают: это что-то из 2008 года, SameSite всё закрыл. На деле SameSite=Lax не покрывает GET-based операции, а фреймворки продолжают оставлять дыры в реализации токенов - не привязывают к сессии, не проверяют при отсутствии, принимают пустые значения. Мой подход: каждый CSRF PoC в отчёте сопровождается полной цепочкой до максимального импакта. Не "можно изменить email", а "изменение email -> сброс пароля -> доступ к API-ключам -> компрометация платёжной интеграции". Когда клиент видит цепочку, severity из Low превращается в High за один звонок.
Отдельная проблема - Client-side CSRF, описанная в OWASP Prevention Cheat Sheet, но практически не тестируемая в индустрии. JavaScript берёт URL-параметр и формирует AJAX-запрос внутри origin - стандартные проверки слепы. Я вижу это в каждом третьем SPA на React или Angular, и ни один автоматический сканер это не детектирует.
Fetch Metadata headers станут основным механизмом защиты в новых приложениях - Go уже включил поддержку в стандартную библиотеку. Но legacy останется с классическими токенами ещё долго, а значит методология тестирования CSRF из пентеста никуда не уходит. Если хотите отработать построение PoC и обход защит на живых стендах - на курсе WAPT эту цепочку проходят в нескольких модулях с лабами.
Последнее редактирование модератором: