Статья CSRF атака: механика эксплуатации, обход защит и построение PoC

Руки в перчатках держат ноутбук над тёмным антистатическим ковриком, на экране виден HTML-код формы автоотправки. Рядом светится монитор с перехваченным запросом в Burp Suite.


На пентесте финтех-платформы разработчики клялись, что CSRF атака невозможна - "у нас настроен CORS". Через 40 минут был готов рабочий PoC: автоматически отправляемая POST-форма меняла email привязки аккаунта. CORS к этому запросу не имел никакого отношения - браузер честно отправлял сессионную cookie вместе с "простым" запросом, а сервер не проверял ничего, кроме неё. Типичная история с Cross-Site Request Forgery: уязвимость считают закрытой, хотя на деле она замаскирована неверным пониманием того, как работает браузер.

Как работает CSRF атака - механика для пентестера​

Межсайтовая подделка запроса (CSRF, она же XSRF, "sea-surf", session riding) - атака, при которой браузер жертвы выполняет HTTP-запрос к целевому веб-приложению от имени аутентифицированного пользователя. Жертва не вводит данные, не нажимает "Подтвердить" - браузер делает это сам, потому что прикрепляет cookies к каждому запросу на соответствующий домен. Подробнее - в нашем подробном разборе пентест веб-приложений.

Согласно описанию PortSwigger, для CSRF атаки нужны три условия:
  1. Целевое действие - в приложении есть функция, которую атакующий хочет вызвать: смена пароля, перевод средств, изменение email, добавление администратора.
  2. Сессия на cookies - приложение идентифицирует пользователя исключительно по сессионной cookie без дополнительных проверок.
  3. Предсказуемые параметры - все параметры запроса известны атакующему заранее. Если для смены пароля требуется ввод текущего пароля - CSRF не сработает.
Все три на месте - перед вами подтверждённая 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 с последующим сбросом пароля.
Kill chain целиком: социальная инженерия (доставка ссылки через email, мессенджер, форум) -> жертва открывает вредоносную страницу -> браузер отправляет запрос с session cookie -> сервер выполняет действие -> атакующий получает контроль (смена email -> сброс пароля -> полный захват аккаунта).

[Применимо: внешний пентест, любые веб-приложения с cookie-based аутентификацией. Во внутреннем пентесте - CSRF на административные панели (роутеры, принтеры, CI/CD), доступные через localhost]

CSRF пример атаки: от GET до POST​

1782573999778.webp

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">
Браузер пытался "загрузить картинку", отправляя GET-запрос с cookies. Пользователь терял контроль над учёткой - пароль менялся молча. Прямое нарушение RFC 2616: GET и HEAD не должны производить побочные эффекты. Но кого это когда останавливало.

Построение 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>
Жертва открывает страницу - JavaScript мгновенно отправляет форму. Браузер прикрепляет session cookie, сервер меняет email. Дальше атакующий запрашивает сброс пароля на свой email и получает полный контроль над аккаунтом.

В 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: где ломаются механизмы​

1782574073859.webp

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом 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​

1782574091192.webp

Согласно 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 запросах:

ЗначениеПоведениеУровень защиты
StrictCookie не отправляется при любых cross-site запросахПолный, но ломает UX (ссылки из email без авторизации)
LaxCookie отправляется при навигации верхнего уровня (GET)Частичный - не покрывает GET-based CSRF
NoneCookie отправляется всегда (требуется 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 уязвимость​

Чеклист, пригодный для передачи в отчёт по пентесту:
  1. Определите state-changing эндпоинты - всё, что меняет данные: POST, PUT, DELETE, а также GET с побочными эффектами. Приоритет: смена email, пароля, прав доступа, финансовые операции.
  2. Проверьте наличие CSRF токена - ищите скрытое поле или кастомный заголовок в запросе. Нет - конструируйте PoC.
  3. Удалите токен из запроса - в Burp Repeater уберите параметр с токеном. Сервер принял запрос? Защита формальна.
  4. Подставьте чужой токен - зарегистрируйте второй аккаунт, возьмите его токен. Если работает - токен не привязан к сессии.
  5. Смените HTTP-метод - замените POST на GET, перенесите параметры в URL. Проверьте, применяется ли валидация.
  6. Проверьте атрибуты session cookie - SameSite=None или отсутствие атрибута в не-Chromium браузерах расширяет вектор.
  7. Подавите Referer - если защита строится на проверке Referer, добавьте в PoC: <meta name="referrer" content="no-referrer">.
  8. Проверьте Login CSRF - подделайте запрос на вход под учёткой атакующего.
  9. Постройте и подтвердите PoC - Burp Suite -> Generate CSRF PoC -> откройте в браузере с авторизованной сессией -> подтвердите выполнение действия.
  10. Документируйте полную цепочку импакта - в отчёте: "смена email -> сброс пароля -> полный захват аккаунта", не абстрактное "обнаружена CSRF уязвимость".
Для автоматизации первичной проверки: в Burp Suite включите Scanner на активное сканирование - он находит отсутствие 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 эту цепочку проходят в нескольких модулях с лабами.
 
Последнее редактирование модератором:
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🧭 Навигатор · ИБ 2026
Не знаешь, какой трек твой?
5 направлений ИБ, реальные зарплаты и точка входа для каждого — в одном треде.
JuniorSenior+
100K → 600K+ ₽ /мес
Открыть навигатор →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab