Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году
Делимся решением задач от CTF-турниров со всего мира. Пригодится как опытным, так и начинающим специалистам по кибербезопасности.
В мире кибербезопасности есть такое событие, как CTF-турнир (Capture The Flag). В нем участники захватывают флаги соперников и защищают свои. Организаторы «зашивают» их в специально разработанные системы — заполучить флаги можно только через взлом. В подборке рассказываем о прошедших CTF-турнирах и делимся решением самых интересных задач по мнению Ивана, специалиста по кибербезопасности в Selectel.
Быстрая навигация по тексту:
KnightCTF 2024, часть 1
В январе прошел CTF-турнир KnightCTF 2024, который организовали cybersecurity-энтузиасты из Бангладеша. За 24 часа участникам предстояло решить задачи из категорий cryptography, reverse engineering, digital forensics, pwn, steganography, web и networking. Коллеге особенно понравилась задача Gain Access 2 из веб-технологий. Ниже рассказываем, как он ее решил.
Решение задачи
Условие
У вас есть все, чтобы достичь цели. Рыцарь указал вам путь. Следуйте по нему и получите флаг. Имейте в виду, что все задачи основаны на реальных ошибках в приложениях.
Решение
Дана форма авторизации:
В коде страницы закомментирован путь к файлу. Переходим к нему и видим послание.
На странице выводится или логин/пароль, или логин/куки — надо проверить оба варианта. Возвращаемся на форму авторизации и вводим полученные данные — логин или пароль неверный.
Заходим в исходный код страницы и видим условие: если авторизация пройдет успешно, пользователя перенаправят на страницу 2fa.php.
Включаем Burp в режим proxy и делаем запрос к 2fa.php. В перехваченном запросе меняем кукки на полученное ранее значение d05fcd90ca236d294384abd00ca98a2d. После — переходим на страницу для ввода второго значения:
Аналогично предыдущему условию работаем с dashboard.php. Делаем запрос к dashboard.php, подменяем в Burp кукки и попадаем на страницу с флагом. Готово — задача решена.
KnightCTF 2024, часть 2
Продолжение материала о KnightCTF 2024, но уже с другими задачами из категории networking. Некоторые из них повторяют предыдущие — так, например, автор находит скрытый флаг в уже известном .pcap-файле и использует bash-history TCP-протокола. Последнее было в задаче Confidential, рассказываем о ней подробнее.
Решение задачи
Условие
Здесь есть что-то конфиденциальное. Сможете ли вы найти его? Пожалуйста, воспользуйтесь приложением к первой задаче. Формат флага: KCTF{fl4G}
Дано
Решение
Возвращаемся к bash-history из предыдущей задачи. На 162 строке видим скачанный архив maybeconfidential.zip. Узнаем, что в нем находится.
В Wireshark можно получить файлы, которые передавались в момент записи дампа трафика. Выбираем Файл → Экспортировать объекты → HTTP, добавляем в фильтр maybeconfidential.zip:
Распаковываем архив. Внутри — .docx-файл с картинкой. На ней изображен маскот рыцаря Knight CTF 2024:
Формат .docx — тоже архив, нужно его открыть. В нем находятся три папки с файлами и XML-документ.
С помощью обычного поиска по тексту ищем в названии KCTF. Находим флаг в maybeconfidential/maybeconfidential/word/document.xml. Задача решена!
DiceCTF 2024 Quals
В начале февраля команда DiceGang провела квалификацию DiceCTF 2024 Quals. Это был Jeopardy-турнир длительностью 48 часов. Все задачи были распределены по пяти направлениям: crypto, misc, pwn, rev и web. В последнем запомнилась задача dicedicegoose: из-за неоднозначного условие было непонятно, что нужно делать. Посмотрим, какое решение нашел наш коллега Иван.
Решение задачи
Условие
Следуйте за лидером.
Дано
Решение
Дана только ссылка на сайт. Переходим на нее и видим такую игру:
Заходим в исходный код страницы, а там — полотно из JavaScript-кода. Не будем приводить здесь весь листинг, вместо этого покажем интересные куски, за которые можно зацепиться. Перед нами — переменные player и goose с числовыми значениями.
Из кода понятно, что переменные — это массивы с исходными координатами красного кубика и черного квадрата. Расположим их в массиве history:
Далее видим блок с изменениями координат player и goose:
Изменение координат player происходит нажатием клавиш W, A, S, D. Координаты goose меняются на единицу в сторону, выбранную случайным образом. И после каждого изменения — добавляются в history.
То есть игрок двигает кнопками красный кубик, тогда как черный квадрат перемещается случайным образом. А массив history сохраняет всю историю координат.
Из кода видно, что игрок выигрывает, если координаты player и goose совпадают:
Чтобы получить флаг, нужно набрать девять очков и с помощью вызова функции передать в нее массив history:
Функция encode выглядит следующим образом:
Каждое нажатие клавиши управления добавляет одно очко. Получается, выигрыш возможен только в сценарии, когда красный кубик двигается вниз, а черный квадрат — влево.
Для примера прикрепляем результат игры без изменения кода, а также содержимое массива history и результат выполнения функции encode:
Логика игры понятна. Теперь нужно получить массив history определенного вида, чтобы захватить флаг. Есть два варианта, как это сделать. Можно через консоль задать значение массива и вызвать функцию encode или просто набрать девять очков в игре. Мы пойдем по второму пути.
Сохраняем index.html и меняем в блоке кода координаты черного квадрата так, чтобы за каждый ход он двигался только влево:
Переходим из кода страницы в Source, затем — в Override. В браузере заменяем index.html отредактированным JavaScript-кодом.
Перезагружаем страницу, нажимаем девять раз S и получаем результат:
Далее идем в консоль, вызываем функцию encode и передаем в нее аргумент history, чтобы получить недостающую часть флага. Готово!
0xL4ugh CTF 24
Третий CTF-турнир, но от команды 0xL4ugh из Египта. В течение суток участникам нужно было решить 36 заданий из категории web, DFIR, reverse, crypto, pwn, misc и osint. По сравнению с предыдущими турнирами задачи здесь сложнее: они основаны на реальных случаях и исследованиях. Ниже делимся решением Simple WAF — задачей по веб-технологиям.
Решение задачи
Условие
Я внес в белый список входные значения, так что, думаю, я в безопасности: P
Дано
Решение
По ссылке — форма из предыдущего задания:
Переходим к файлам из предыдущего архива.
В init.db нам предлагают создать таблицу с тремя столбцами.
После — добавить запись об администраторе:
На этот раз пароль — не admin. Хеш md5 к нему не подходит.
Перейдем к index.php. В нем есть ряд условий:
Если username и password в отправленном POST-запросе не пустые, то идет проверка в функции waf:
Дальнейшая обработка запроса происходит только в том случае, если проверка в waf прошла неуспешно. После этого она выполняет запрос к базе данных:
Если в результате запроса к БД количество строк равняются единице, то мы получим флаг. Для этого необходимо выполнить два условия.
1. Данные в username не должны попадать под шаблон:
2. Запрос к БД должен вернуть только одну строку:
Поскольку в базе данных находится одна строка, то обход авторизации должен сработать независимо от переданных значений username, password. Добавляем еще одно условие, чтобы обойти авторизацию:
Данные в username не должны попадать под шаблон preg_match("/([^a-z])+/s",$input). В нем происходит проверка наличия букв: если они есть, то запрос к БД не доходит. Поэтому стоит учитывать фильтр preg_match php, чтобы не допустить появление букв в username.
Прикладываем пример альтернативного обхода авторизации, который не содержит букв:
В итоговой SQL-инъекции меняем or 1=1-- — на || 1=1-- -.
Теперь необходимо решить, как обойти preg_match. По ссылке можно найти информацию о функции и ее ошибках. Делаем вывод, что preg_match, являясь PCRE-функцией, рекурсивно проверяет переданную строку по шаблону и имеет предел входных значение. Информацию о лимитах функции можно найти в руководстве по PHP. При превышении этого значения waf вернет false, что нам и требуется.
Собираем POST-запрос в Python и передаем его в тестовое приложение, запущенное в Docker. Собираем образ Docker и запускаем контейнер:
Пишу скрипт test_waf.py.
Интересное наблюдение: при 10’001 символов функция возвращает false. Возможно, это значение может быть еще ниже, его можно определить параметром pcre.recursion_limit в php.ini.
В ответе получаем страницу с тестовым флагом:
Теперь этим скриптом можно обратиться к основному приложению, чтобы получить флаг. Готово!