• Твой профиль заполнен на 0%. Заполни за 1 минуту, чтобы тебя нашли единомышленники и работодатели. Заполнить →

Статья CTF Web Writeup: SQL injection, SSRF и десериализация — разбор реальных задач

Вскрытый USB-накопитель на антистатическом коврике с видимой платой и тремя миниатюрными иглами рядом. Тёплый янтарный свет лампы контрастирует с холодным бирюзовым отсветом монитора.


Веб - самая популярная и самая непредсказуемая категория в CTF. Тут нет формулы «запустил скрипт - получил флаг». Каждый таск - мини-приложение со своей логикой, и побеждает тот, кто быстрее находит отклонение от нормы. За последние пару лет я прошёл несколько сотен веб-тасков на HackTheBox, PicoCTF и Intigriti Monthly Challenges, и вижу три устойчивых столпа: SQL injection, SSRF и insecure deserialization. Обёртки меняются, ядро эксплуатации - нет.

В этом CTF web writeup разберу конкретные подходы с реальных соревнований - с объяснением, почему первые попытки обычно проваливаются и что с этим делать.

Методология разбора веб-тасков в CTF​

Прежде чем лезть в конкретные уязвимости, нужна система. Без неё ты потратишь час на фаззинг директорий, когда ответ лежит в комментарии к HTML. Вот мой порядок действий на любом веб-таске:

Первые 60 секунд - открываю сайт в браузере, смотрю исходный код страницы (Ctrl+U), проверяю заголовки ответа через DevTools. Часто прямо в комментариях или в заголовке X-Powered-By уже видно стек: PHP, Flask, Express. Это сразу сужает вектор. На SANReN CTF, например, рекомендуют пробовать обращаться к index.php, index.cgi, index.html - по ответу сервера понятно, что под капотом.

Следующие 5 минут - если есть исходный код (а в современных CTF его всё чаще дают), читаю серверную часть. Не весь код, а точки входа: роутеры, обработчики POST-запросов, middleware. Именно тут прячутся инъекции. Если исходников нет - запускаю ffuf -u http://target/FUZZ -w common.txt для поиска скрытых эндпоинтов, включая .git/HEAD, /backup, /admin.

Когда нашёл потенциальный вектор - проверяю вручную через Burp Suite, а не сразу бросаю автоматику. На MireaCTF, по данным одного из writeup'ов с Codeby, sqlmap не смог найти blind SQL injection в cookie, хотя уязвимость подтвердилась вручную за минуту. Автоматика хороша, но она не понимает кастомную логику конкретного таска.

В терминах MITRE ATT&CK весь этот процесс - Exploit Public-Facing Application (T1190, Initial Access). Но в CTF мы обычно идём дальше: после первичной эксплуатации нужно прочитать файл, достучаться до внутреннего сервиса или поднять привилегии.

SQL Injection в CTF задачах​

SQLi остаётся самой частой уязвимостью в веб-категории CTF. Но если ты думаешь, что это про ' OR 1=1 -- - значит давно не играл. Современные таски строятся вокруг обходов фильтров, нестандартных точек инъекции и ситуаций, где автоматика бессильна.

UNION-based: когда видишь вывод​

Классический сценарий: приложение выводит данные из базы на страницу, и ты можешь «подклеить» свой запрос через UNION SELECT. Первое, что нужно определить - количество колонок. Я делаю это через ORDER BY N, увеличивая N, пока не получу ошибку. Нашёл, что колонок 3? Отлично: ' UNION SELECT 1,2,3-- - и смотришь, какая цифра отобразилась на странице. Та позиция - твоё «окно» для извлечения данных.

Типичная ошибка новичков - пытаться сразу читать таблицу users. В CTF структура базы может быть любой. Сначала узнай имена таблиц через information_schema.tables, потом колонки через information_schema.columns. Да, это базовый SQL, но количество людей, которые пропускают этот шаг и тратят время на угадывание, до сих пор поражает.

На HackTheBox в машине Gavel (по данным 0xdf) была интересная штука: SQL injection через PDO с backtick-quoted prepared statements. Стандартные payloads не работали - нужно было разобраться, как именно PDO экранирует входные данные, и найти обход конкретно для backtick-quoting. Вот почему чтение исходников важнее автоматического сканирования.

Blind SQL injection: когда вывода нет​

Если приложение не показывает результат запроса, но по-разному реагирует на true/false условия - это blind SQLi. На MireaCTF один из тасков содержал именно такую уязвимость в cookie: при true на странице появлялось слово «Welcome», при false - нет.

Алгоритм ручной эксплуатации: формируешь условие ' AND SUBSTRING(password,1,1)='a'-- и перебираешь символы. Руками это мучительно медленно, поэтому пишешь скрипт. Но вот подвох: sqlmap с параметрами --technique=B --dbms=MySQL --level=5 --risk=3 на том же таске не сработал - точка инъекции была в cookie с нестандартной обработкой. Sqlmap ожидает определённые паттерны ответов, и когда приложение ведёт себя нетипично, приходится писать кастомный эксплойт.

Для sqlmap в таких случаях стоит попробовать сохранить запрос в файл через Burp (sqlmap -r request.txt) и пометить точку инъекции символом * прямо в значении cookie. Полезны флаги --string="Welcome" (текст при true) и --not-string="Error". Но если и это не помогает - пиши свой скрипт на Python с requests, бинарным поиском по ASCII-кодам и выводом в реальном времени. Честно, это быстрее, чем воевать с автоматикой.

Извлечение данных через blind SQLi - результат эксплуатации T1190 (Exploit Public-Facing Application, Initial Access). T1213.006 (Databases) описывает сбор данных при легитимном доступе к СУБД, что не про инъекцию.

SSRF в CTF задачах - разбор эксплуатации​

Server-Side Request Forgery - мой любимый класс уязвимостей в CTF. Причина проста: SSRF редко бывает конечной целью. Это всегда трамплин - к чтению внутренних файлов, к метаданным облака, к внутренним сервисам без аутентификации. По MITRE ATT&CK начальная эксплуатация SSRF - T1190 (Exploit Public-Facing Application, Initial Access). Сканирование внутренних портов через SSRF (как в примере с Jenkins) - T1046 (Network Service Discovery). T1090 (Proxy) - это про маршрутизацию C2-трафика, к механике SSRF отношения не имеет.

SSRF через Next.js Middleware: реальный кейс CVE-2025-57822​

Один из лучших примеров SSRF в CTF - задача CatFlix AI с Intigriti Monthly Challenge (август 2025). Приложение на Next.js, исходный код приложен. Вот как шёл ход мыслей, когда я разбирал аналогичную задачу.

Первое, что бросается в глаза в middleware.ts - код добавляет security headers к ответу. Но есть блок, который проверяет UTM-параметры в запросе. Когда utm_source присутствует, NextResponse.next() вызывается без явной передачи объекта request - и пользовательские заголовки пробрасываются на сервер некорректно. Передача заголовка Location вызывает серверный редирект к произвольному URL. Три строчки кода - и вот тебе SSRF.

Это CVE-2025-57822 - SSRF в Next.js до версий 14.2.32 и 15.4.7, CVSS 6.5 (MEDIUM), вектор CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N. Корневая проблема - CWE-918 (Server-Side Request Forgery). Сложность атаки помечена как High (AC:H), потому что нужна специфическая конфигурация middleware - когда next() вызывается без передачи request object. В реальных условиях CVSS 6.5 (MEDIUM) отражает именно это ограничение: при пентесте нужно сначала подтвердить наличие уязвимой конфигурации. В CTF авторы задачи это условие гарантируют - спасибо им за это.

Proof of concept элементарный: отправляешь GET-запрос с ?utm_source=meta и заголовком Location: http://localhost:3000/, и вместо нормального ответа получаешь содержимое внутреннего сервиса. По данным writeup'а с Intigriti, далее через перебор портов (ffuf по диапазону) был найден Jenkins на порту 8080 без аутентификации, а через его Groovy Script Console - выполнение команд:
Код:
GET /?utm_source=meta HTTP/2
Host: challenge-0825.intigriti.io
Location: http://localhost:8080/script
Content-Type: application/x-www-form-urlencoded

script=println('cat /app/flag.txt'.execute().text)
Цепочка SSRF → internal service discovery → RCE через Jenkins - классический паттерн, который встречается не только в CTF, но и на реальных проектах.

Обход фильтров localhost в CTF​

Если авторы таска не полные новички, прямой http://localhost будет заблокирован. Вот техники обхода, которые работали у меня на реальных соревнованиях:

DNS rebinding и альтернативные представления IP. Вместо 127.0.0.1 пробуй http://127.1, http://0x7f000001, http://2130706433 (decimal), http://[::1] (IPv6). На MireaCTF один из тасков отдавал флаг при обращении к http://0:80/flag - фильтр проверял строку «localhost» и «127.0.0.1», но не альтернативные записи. На заборе тоже написано «вход запрещён».

Редиректы через внешний сервер. Поднимаешь свой сервер, который возвращает 302 на http://127.0.0.1:порт. Приложение проверяет URL до запроса, видит твой домен - всё чисто. Но HTTP-клиент следует за редиректом и попадает на localhost. Для быстрого решения на CTF подходит ngrok или одноразовый Flask-сервер.

URL-схемы. Если SSRF реализован через curl или аналог, пробуй file:///etc/passwd для чтения локальных файлов. Или gopher:// для отправки произвольных TCP-пакетов - это позволяет, например, отправить SQL-запрос напрямую в MySQL на localhost:3306 без клиента. Gopher в 2025 году - кто бы мог подумать, что эта штука из 90-х ещё пригодится.

Десериализация в CTF - уязвимость, которую часто пропускают​

Insecure deserialization (CWE-502: Deserialization of Untrusted Data, категория A08:2021 - Software and Data Integrity Failures по OWASP Top 10 2021) - это когда приложение восстанавливает объект из пользовательских данных без валидации, и атакующий может подменить данные объекта или вызвать цепочку деструктивных методов. В CTF это встречается в двух основных вариантах: PHP и Java.

PHP: магические методы как точка входа​

В PHP десериализация через unserialize() опасна из-за магических методов. При вызове unserialize() PHP дёргает [B]wakeup() (или [/B]unserialize() в PHP 8.0+, который имеет приоритет). Когда объект уничтожается - __destruct(). Если в любом из этих методов есть обращение к файловой системе, выполнение команд или запись данных - это потенциальный RCE.

Моя методика на CTF: нахожу вызов unserialize() в исходниках, затем ищу все классы с [B]destruct, [/B]wakeup, [B]toString, [/B]call. Строю «gadget chain» - цепочку объектов, где свойство одного является объектом другого класса, и при вызове магического метода срабатывает нужная мне логика. Типовой пример: класс Logger с __destruct(), который записывает $this->logFile с содержимым $this->logData. Подменяем logFile на путь к PHP-файлу, logData - на вебшелл, сериализуем, отправляем - и получаем Web Shell (T1505.003, Persistence по MITRE ATT&CK). Красота.

Ещё один важный момент - формат phar://. Даже если прямого вызова unserialize() нет, но есть файловая операция (file_exists(), fopen(), is_dir()), можно загрузить PHAR-архив с сериализованным объектом в метаданных. При обращении к phar://uploads/evil.jpg PHP автоматически десериализует метаданные - сам, без спроса. Эта техника использовалась, по данным коллекции best-web-ctf-writeups на GitHub, в задаче PDF Creator с CCCamp 2019.

Java: ysoserial и реальность​

Java-десериализация - тяжёлая артиллерия. Если приложение принимает сериализованные Java-объекты (маркер AC ED 00 05 в hex или rO0AB в Base64), это почти гарантированный RCE при наличии уязвимых библиотек в classpath.

Инструмент ysoserial генерирует payload для известных gadget chains: CommonsCollections, Spring, Groovy и других. Оригинальный ysoserial (frohoff) не поддерживается с ~2021 года и несовместим с Java 17+ из-за ограничений модульной системы - используйте актуальные форки (ysoserial-all, java-deserialization-scanner в Burp). На практике в CTF запускаю java -jar ysoserial.jar CommonsCollections5 'команда' | base64 и подставляю результат в уязвимый параметр. Учтите: CommonsCollections5 работает только с commons-collections 3.x; для 4.x нужны chains CommonsCollections2 или CommonsCollections4.

Но не всё так просто - нужно угадать, какая библиотека есть на сервере. Если исходники доступны, смотрю pom.xml или build.gradle на наличие commons-collections, spring-core, groovy. Если нет - тупо перебираю основные chains. Метод научного тыка, но работает.

На HackTheBox (по данным 0xdf) машины с Java-десериализацией - например, Cereal, Ophiuchi - часто требуют не просто запуск ysoserial, а понимание того, как сериализованные данные обрабатываются конкретным фреймворком. В Ophiuchi уязвимость была в SnakeYAML - парсере YAML для Java, где специально сформированный YAML-документ вызывал загрузку произвольного класса. Десериализация - она такая: вроде один класс уязвимостей, а копнёшь - каждый раз новый зоопарк.

Чейнинг уязвимостей - когда одной баги мало​

В хороших CTF задачах один баг не даёт флаг. Нужна цепочка. Вот комбинации, которые я встречал чаще всего:

SSRF + внутренний сервис без аутентификации. Уже разобрали на примере CatFlix AI: SSRF в Next.js middleware → Jenkins без пароля → Groovy RCE. Ищи на внутренних портах Redis (6379), Elasticsearch (9200), Docker API (2375), Kubernetes API (6443).

SQL injection + file read/write. В MySQL через LOAD_FILE('/etc/passwd') или INTO OUTFILE '/var/www/html/shell.php' можно перейти от чтения базы к RCE. Нужны привилегии FILE, но в CTF они часто есть. Проверь значение @@secure_file_priv через UNION SELECT @@secure_file_priv - если пустое ('') - ограничений нет; если указан путь - запись/чтение только в той директории; если NULL - FILE-операции запрещены полностью. В MySQL 8.0+ значение по умолчанию - /var/lib/mysql-files/.

LFI + log poisoning. Нашёл Local File Inclusion, но нет интересных файлов? Отправь запрос с PHP-кодом в User-Agent, затем подключи лог Apache через LFI: ?page=../../../var/log/apache2/access.log. PHP-код из User-Agent выполнится при подключении лога. Грязный трюк, но работает как часы.

NoSQL injection + SSRF. На CatFlix AI помимо SSRF была ещё и NoSQL injection в эндпоинте регистрации - raw input конкатенировался в MongoDB-запрос. Сама по себе NoSQLi не давала доступ к файловой системе, но в комбинации с SSRF позволяла бы манипулировать данными для дальнейшей эскалации.

На машине HTB Guardian (по данным 0xdf) цепочка была ещё длиннее: IDOR в чат-функции → утечка credentials для Gitea → анализ исходного кода → XSS через malicious XLSX в PhpSpreadsheet → кража сессии → CSRF для создания admin-аккаунта → LFI с PHP filter chain injection → RCE. Шесть звеньев. Именно такие задачи отличают средний CTF от топового.

Практический чеклист для CTF Web категории​

Мой пошаговый алгоритм, который экономит время на соревнованиях. Каждый пункт - конкретное действие, а не «подумайте о безопасности».
🔓 Эксклюзивный контент для зарегистрированных пользователей.

Частые ошибки и почему первые попытки проваливаются​

За годы CTF я выделил паттерны, которые стабильно ломают новичков:

Попытка всё автоматизировать. Sqlmap, Nikto, dirsearch - отличные инструменты, но они не замена мозгу. На MireaCTF sqlmap не нашёл blind SQLi в cookie, а ручная проверка подтвердила уязвимость за минуту. Автоматика - усилитель, не костыль.

Игнорирование исходного кода. Если таск даёт исходники - это подсказка. Не проскакивай мимо. На Intigriti CatFlix AI ключ к решению был в трёх строчках middleware.ts. Я видел людей, которые фаззили директории 40 минут, когда уязвимость лежала в открытом коде. Обидно.

Зацикливание на одном векторе. Потратил 15 минут на SQLi и ничего? Переключись. Возможно, это не SQLi, а SSTI. Или command injection. Или IDOR. Диверсифицируй проверки в первые 5 минут, потом углубляйся в самый перспективный вектор.

Непонимание формата флага. Звучит глупо, но регулярно люди находят строку, похожую на флаг, и не могут её сдать - не обернули в формат CTF{...} или не декодировали из Base64. Всегда проверяй, нет ли дополнительного слоя кодирования - Deobfuscate/Decode Files or Information (T1140, Defense Evasion) работает и в обратную сторону.

Привязка к реальному пентесту​

Всё, что описано выше, напрямую переносится в реальные аудиты. SQL injection в CTF - это та же SQL injection в продакшн-приложении банка. SSRF через Next.js middleware (CVE-2025-57822) - реальная CVE, которая затрагивала self-hosted приложения на Next.js до версий 14.2.32 и 15.4.7. Десериализация в PHP и Java - причина множества критических взломов, от Apache Struts до Magento.

Разница в том, что в CTF тебе дают чёткие границы и легальное разрешение. В реальном пентесте добавляется scope, согласование и отчётность. Но навык видеть уязвимость в коде за 10 минут - один и тот же.

Хочешь прокачаться в веб-категории - решай задачи на HackerLab (секция Web), участвуй в Intigriti Monthly CTF, разбирай writeup'ы с CTFtime. И главное - пиши свои writeup'ы. Пока не объяснишь решение другому человеку, ты его не усвоил. Потренировавшись на кошках - можно и на реальный проект.
 
Последнее редактирование модератором:
Мы в соцсетях:

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

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab