Привет, Хабр! Давно я не публиковал свои похождения по CTF — пора это исправить! За прошедшее время мне удалось попробовать себя в трех турнирах. В ImaginaryCTF, посвященном кибербезопасности, Umasscybersec, адаптированном под Губку Боба, и n00bzCTF, который сделали для опытных n00bz.

Категорий задач на турнирах было много. Среди них — OSINT, Reverse, Forensics, Pwn и Web. Последнее направление для меня самое интересное, поэтому его я и выбрал. Что из этого получилось, рассказываю под катом!

Readme

Задание

Попробуйте прочитать файл file.txt.

readme.tar.gz →

readme.chal.imaginaryctf.org →

Решение

1. Перехожу по ссылке и убеждаюсь, что она работает. Открываю код страницы. 

2. В коде страницы ничего интересного — надо искать глубже. Запускаю dirsearch.

Сервер отвечает ошибкой 404 на запросы страниц flag.txt и index.html. А в файле default.conf с настройками nginx находится такая конфигурация:

server {
    listen       80 default_server;
    listen  [::]:80;
    root /app/public;

    location / {
        if (-f $request_filename) {
            return 404;
        }
        proxy_pass http://localhost:8000;
    }
}

В директиве location есть условие проверки наличия файла. Если запрашиваемый в URL файл существует, то сервер возвращает ошибку 404. Результаты поиска dirsearch и эта конфигурация подтверждают, что на сервере есть оба файла: flag.txt, index.html. Необходимо как-то обойти это условие — есть несколько вариантов.

3. Вручную или с помощью модуля Intruder в burpsuite можно перебрать предлагаемые варианты нагрузок. В результате вместо /flag.txt можно запросить страницу по такой форме: 

GET /flag.txt/.

4. Сервер вернул внятный ответ — флаг в кармане!

Journal

Задание

Дорогой дневник, в этом приложении нет LFI. 

journal-dist.zip →

journal.chal.imaginaryctf.org → 

Решение

На страницы находятся лишь ссылки на пять файлов с текстами. Однако наибольший интерес вызывает URL:

http://journal.chal.imaginaryctf.org/?file=file1.txt

Имя запрашиваемого файла передается в параметре file, который потенциально может быть уязвимым.

1. Смотрю приложенные конфигурационные файлы. Начинаю с index.php.

В нем находятся те самые ссылки на текстовые файлы, а также некая функция assert, которая вместе с strpos проверяет наличие многоточия в имени запрашиваемого файла. Вероятно, так реализована защита от cmd-инъекции. Далее просто проверяется наличие файла и отдается содержимое, если условие истинно.

2. По логике кода понятно, что можно попробовать обратиться по пути flag.txt. Иду смотреть Dockerfile.

Самая интересная строка в файле выглядит так:

RUN mv /flag.txt /flag-’tr -dc A-Za-z0-9 < /dev/random | head -c 20’.txt

В имя файла flag.txt добавляется двадцать случайных символов. При этом сам файл с флагом находится в корневом каталоге. Значит, чтобы добраться до ОС сервера, необходимо работать с функцией:

assert("strpos('$file', '..') === false") or die("Invalid file!");

3. Так как передаваемое в параметр file значение используется в функции strpos без какой-либо валидации, можно провести php-инъекцию. Она будет выглядеть следующим образом: 

‘).system(“ls”);//

В результате строка кода примет такой вид:

assert("strpos('‘).system(“ls”);//', '..') === false") or die("Invalid file!");

Все, что будет после знака комментария «//», программа проигнорирует. До него будет выполнено strpos(‘’).system(‘ls’). Кроме того, должна появиться ошибка выполнения функции assert, т. к. она принимает два параметра.

4. Пробую подать нагрузку: 

Все работает — файлы текущего каталога отдаются.

5. Поднимаюсь по файловой системе до корневого каталога, где должен находиться файл с флагом.

Действительно, файл есть. Брутить имя, конечно, было бы очень долгим занятием. 

6. Читаю файл и получаю заветный флаг!

Passwordless

Задание

Устали от хранения паролей? Не беспокойтесь! Этот сверхзащищенный веб-сайт не содержит паролей!

app.py → 

24.199.110.35:40150 →

Решение

На странице задания встречает такая форма ввода логина:

Если ввести любое имя, то происходит редирект.

1. В коде страницы нет ничего интересного — иду в исходники. В app.py есть следующий скрипт маршрутизации запроса:

@app.route('/<uid>')
def user_page(uid):
    if uid != str(uuid.uuid5(leet,'admin123')):
        return f'Welcome! No flag for you :('
    else:
        return flag

Флаг отдается только в случае, если перейти по ссылке:

str(uuid.uuid5(leet,'admin123'))

В самом начале файла задается значение переменной leet:

global leet=uuid.UUID('13371337-1337-1337-1337-133713371337')

2. Пробую сформировать целевую ссылку — для этого в IDLE выполняю часть кода из конфигурационного файла:

import uuid

leet=uuid.UUID('13371337-1337-1337-1337-133713371337')

print(uuid.uuid5(leet,'admin123'))

На выходе получаю следующую строку:

3c68e6cc-15a7-59d4-823c-e7563bbb326c

3. Используя полученную строку, собираю новую ссылку и пробую обратиться по ней: 

http://24.199.110.35:40150/3c68e6cc-15a7-59d4-823c-e7563bbb326c

Готово — сервер возвращает флаг: n00bz{1337-13371337-1337-133713371337-1337}.

Holesome Birthday Party

Задание

Тебя только что пригласили на день рождения Губки Боба! Но он решил испытать вашу дружбу, прежде чем выдать входной билет. Сможешь ли ты доказать пройти испытания и заслужить право попасть на вечеринку?

holesomebirthdayparty.ctf.umasscybersec.org →

Решение

Губка Боб ждет на своем дне рождения только настоящих друзей — надо обязательно попасть на тусовку!

1. Перехожу по ссылке, вижу первое задание:

Похоже, необходимо поработать с HTTP-запросом и в заголовке User-Agent указать «Bikini Bottom»:

User-Agent: Bikini Bottom

Отлично! Первое задание пройдено. 

2. Далее необходимо поменять дату в запросе через заголовок Date. Гуглю — оказывается, у Губки Боба день рождения 14 июля. Меняю дату на нужную.

 

Готово — перехожу к следующему заданию. 

3. Теперь Боб хочет учить французский язык, поэтому просит говорить соответствующе. 

В HTTP-запросе язык указывается в заголовке Accept-Language — меняю на fr:

Прекрасно, двигаюсь дальше.

4. Именинник просит захватить печенье со вкусом шоколадной крошки. Пойду навстречу и добавлю в HTTP-запрос Cookie с требуемым значением.

Губка Боб выдал билет на тусовку, но что-то в нем не так… 

В Cookie прилетает новое значение (Login=eyJsb2dnZWRpbiI6IGZhbHNlfQ==) — логин в BASE-64 кодировке. У Burpsuite есть модуль Decode, воспользуюсь им: 

Меняю на true и отдаю в заголовке. Финальный HTTP-запрос (с учетом предыдущих заданий) будет следующим:

Флаг получен — вечеринка тусовка с грустным Сквидвардом началась!

CTF турниры помогают расширить знания о, казалось бы, понятных функциях языков программирования или конфигурации nginx. А также позволяют поработать со структурой протокола HTTP.

Надеюсь, подобный разбор заданий поможет начинающим игрокам в CTF понять способы подхода к процессу поиска флагов. До встречи в следующих частях!

Комментарии (1)


  1. MrB4el
    15.08.2024 04:37
    +1

    Довольно милый CTF с почти минимум надмозга, характерного для игр последнего десятка лет.

    Спасибо за разбор!