Как мы организовали CTF-турнир для IT-конференции

Как мы организовали CTF-турнир для IT-конференции

Иван Коробов Иван Коробов Ведущий инженер по ИБ 24 октября 2024

Рассказываем о нашей подготовке к мероприятию и задачах с турнира.

Изображение записи

10 октября мы провели свой первый CTF-турнир в рамках конференции Selectel Tech Day 2024. Участникам предстояло разгадать семь задач по информационной безопасности — например, найти в море сокровище, приготовить блюдо при 256 градусов, набрать 6x6x6 в костях и другое. 

Поскольку ИБ — не основной профиль мероприятия, мы решили ограничить количество заданий и сделать их несложными. В тексте рассказываем, как организовали онлайн- и офлайн-стенд для решения тасков и показываем сами задания. 

Офлайн-стенд CTF-турнира.
Офлайн-стенд CTF-турнира.

Подготовка к турниру

В этом году мы сосредоточились на четырех направлениях: серверы и оборудование, облачные технологии, информационная безопасность и машинное обучение. Чтобы организовать интересную активность по нашему профилю, при этом не «загрузить» сложными заданиями посетителей, нам нужно было выполнить несколько требований. 

  • Задания должны варьироваться от очень простых до средних. В первом случае пользователи могут найти решение за один ход, во втором — за два и более.
  • Задания должны быть интересны и под силу тем, кто не разбирается в информационной безопасности.
  • Задания можно решить без специального инструментария. 

Как я упомянул ранее, ИБ — не основной профиль мероприятия, поэтому делать что-то кроме привычных веб-приложений казалось бессмысленным. В качестве инфраструктуры решили использовать продукты Selectel. А чтобы и посетители, и зрители трансляции могли принять участие, организовали два формата турнира: офлайн и онлайн. Расскажем о каждом подробнее. 

Онлайн-стенд

Архитектура онлайн-стенда выглядит следующим образом:

Архитектура онлайн-стенда.
Архитектура онлайн-стенда.

В публичном облаке Selectel развернули три виртуальные машины (далее — ВМ) произвольной конфигурации:

Характеристики облачных серверов. 
Характеристики облачных серверов. 

На каждой разместили по одному сетевому интерфейсу, установили Nginx и Docker. Задание в турнире — это отдельный Docker-контейнер, поэтому на ВМ подняли по семь контейнеров:

Docker-контейнеры.
Docker-контейнеры задач.

Все Docker-образы имели следующую структуру: 


    task__4
├── Dockerfile
├── requirements.txt
└── src
    ├── app.py
    ├── static
    │   ├── images
    │   │   ├── 4.png
    │   │   ├── b-2.png
    │   │   ├── flame.svg
    │   │   ├── img4.jpeg
    │   │   ├── logo-1.svg
    │   │   └── vector.svg
    │   ├── intlTelInput
    │   │   ├── intlTelInput.css
    │   │   ├── intlTelInput.min.js
    │   │   └── utils.js
    │   ├── scripts
    │   │   ├── jquery.js
    │   │   └── main.js
    │   └── styles
    │       ├── main.css
    │       └── task__4.css
    └── templates
        ├── error.html
        └── index.html

Пример четвертого задания. 

В Dockerfile описан порядок сборки контейнеров, а в requirements.txt — пакеты, которые необходимо установить. Приложение в контейнере требует наличие Flask, поэтому в файле указываем только этот фреймворк. 

В каталоге src находится файл app.py. Он содержит логику приложения и каталоги со статичным контентом: картинками, JavaScript, CSS-файлами и HTML-страницами.

Все изображения для заданий сгенерировали с помощью искусственного интеллекта. 

Готовые образы контейнеров расположили в Container Registry

Задачи в Container Registry.
Задачи в Container Registry.

При необходимости их можно быстро доставить на ВМ:


    docker pull cr.selcloud.ru/ctf/task_1:final

Nginx мы использовали как reverse-proxy, чтобы публиковать задачи:


    server {
        server_name deep.slcctf.fun;
        location / {
            proxy_pass       http://127.0.0.1:8001;
        }
}
server {
        server_name 256degrees.slcctf.fun;
        location / {
            proxy_pass       http://127.0.0.1:8002;
        }
}
server {
        server_name secretpath.slcctf.fun;
        location / {
            proxy_pass       http://127.0.0.1:8003;
        }
        location = /flag.txt {
            rewrite     /flag.txt /impasse;
        }
}
server {
        server_name air.slcctf.fun;
        location / {
            proxy_pass       http://127.0.0.1:8004;
        }
}
server {
        server_name future.slcctf.fun;
        location / {
            proxy_pass       http://127.0.0.1:8005;
        }
}
server {
        server_name dice.slcctf.fun;
        location / {
            proxy_pass       http://127.0.0.1:8006;
        }
}
server {
        server_name geo.slcctf.fun;
        location / {
            proxy_pass       http://127.0.0.1:8007;
        }
}

Все ВМ мы подключили к балансировщику нагрузки и настроили балансировку по L4. Белый IP-адрес балансировщика спрятали за фильтр DDoS-Guard.

Балансировщик нагрузки.
Балансировщик нагрузки.

В итоге мы получили надежную облачную инфраструктуру, которую можно масштабировать как за счет увеличения нод с контейнерами, так и самих заданий в новых контейнерах. Конечно, можно было использовать Managed Kubernetes. В следующих задачах, вероятно, перейдем именно на него.

Офлайн-стенд

Не все участники приносят на конференции свои ноутбуки. Есть и смартфоны, но решать задачи с изменением HTTP-запросов на них неудобно. Поскольку у нас уже был стенд по информационной безопасности, мы решили разместить турнир прямо там. Добавили две плазменные панели, соединили их с Raspberry Pi и дополнили периферией. 

К Raspberry Pi подключили систему охлаждения и проводной Интернет. Дополнительно установили Kali Linux, чтобы участники могли использовать любые знакомые и удобные инструменты. Хотя для решения задач достаточно использовать только браузер.

Фото «малинки» со стенда.

Фото «малинки» со стенда.

В результате получили готовый стенд для CTF-турнира:

Офлайн-стенд CTF-турнира.
Офлайн-стенд CTF-турнира.

Задания с турнира

Все задания доступны на странице мероприятия. Решения предварительно спрятали под спойлером, чтобы вы могли выполнить их самостоятельно. Полученные флаги можно сдать Telegram-боту @SelectelTechDayBot.

Deep

Задание

Погрузитесь глубже и найдите сокровище! Формат флага: slcctf{} 

Открываем код страницы с помощью ПКМ и нажимаем на кнопку Просмотр кода страницы. В новом окне ищем ключевое слово slcctf. Среди тегов div находим флаг:

256 Degrees

Задание

Только шеф может брать этот рецепт! Лучше всего блюдо получается при готовке на 256 градусах! Получить рецепт

Формат флага: slcctf{} 

Нажимаем на активную ссылку Получить рецепт и переходим на страницу http://256degrees.slcctf.fun/login с формой авторизации. Подставляем кавычку в поле логина или пароля и видим ошибку — потенциально сервер уязвим к SQL-инъекциям. Ищем нагрузки SQL-инъекций для обхода авторизации. После — подставляем в поле логина или пароля полученное предложение. Обход авторизации выполнен!

На главной странице задания есть условие, что блюдо должно быть приготовлено на 256 градусах. Берем строку «the-best-grill-in-the-whole-world» и считаем для нее хэш sha256:

echo -n the-best-grill-in-the-whole-world | sha256sum

В результате получаем флаг:

slcctf{a90a48277571ea31ff54c0dee577c00077dea703160f7c9464e4469d2724edcf}

Secret Path

Задание

Найдите секретный путь и выйдите к flag.txt. Формат флага: slcctf{} 

Нужно найти путь к flag.txt, поэтому сразу переходим по адресу https://secretpath.slcctf.fun/flag.txt. Флага нет, но есть подсказка: «Похоже, вы заблудились. Вы точно идете, куда хотите?! Сверьте карту: 120 = 209 133».

Далее внимательно читаем описание задания: «Подвалы всегда окутаны загадками и тайнами. Часто они служат не только хранилищем для старых вещей, но и местом для путешествий в мир неизведанного. Надо лишь быть внимаtельными!». Видим, что русскую букву «т» заменили на английскую t.

Две буквы различаются кодировками. Идем искать информацию о кодировках и выясняем, что 120 — это x в ASCII. Аналогично ищем информацию по 209 133 — «х» в ASCII русских символов. Если между ними стоит знак равенства, то в исходном пути меняем один на другой. По пути https://secretpath.slcctf.fun/flag.t%D1%85t получаем флаг:

Air

Задание

Мы ожидаем пилота для вылета! Если вы пилот, срочно пройдите по пути регистрации! Затем поднимайтесь в кабину и готовьтесь к вылету! 

Формат флага: slcctf{} 

Нужно пройти по пути регистрации — добавляем в URL /registration и получаем сообщение: «Who are you? Post your token!» Здесь, очевидно, придется поработать с параметрами HTTP-запроса. Нажимаем ПКМ на странице задания → ИсследоватьNetworkAll и перезагружаем страницу. Во вкладке Headers видим полученные в ответе HTTP-заголовки. Среди них находится заголовок Authorization:

Разделенная на три части точками строка — это JSON Web Token. Попробуем посмотреть содержимое: переходим на https://jwt.io/ и вводим найденный токен:

Возвращаемся в консоль разработчика. В ответе сервера нажимаем ПКМEdit and resend, если используем браузер FireFox. Добавляем в HTTP-запрос заголовок Authorization со значением: «Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoicGlsb3QifQ.fwDzRvtQa-5b_4oFm-kwDxef5qCrUa9zwzdrEMsZUXA» — и меняем метод с GET на POST. Отправляем новый запрос и получаем флаг:

 

Future

Задание

T-Rex! Тебе необходимо отправиться в будущее для поиска ценного сообщения! Я буду давать тебе инструкции по настройке телепорта. Будь внимателен! Для начала укажи точку назначения — локацию ‘Cloudtown’! 

Формат флага: slcctf{} 

Форм ввода данных нет, параметры нам не известны, но есть HTTP-заголовок Location — попробуем передать значение Cloudtown в нем. Как и в предыдущем задании открываем редактор HTTP-запросов в браузере, добавляем новый заголовок с указанным значением, отправляем GET-запрос серверу и получаем ответ:

Отлично, теперь необходимо передать серверу дату и время. На этот раз используем HTTP-заголовок Date и передаем в нем значение: «Sun, 03 Jul 2078 08:42:55 GMT».

Далее меняем язык на «te» в значении заголовка Accept-Language:

Передаем возраст в заголовок Age:

Остался последний шаг. В задании упоминается T-Rex, маскот Selectel. Дата основания компании — 11.09.2008. В заголовке User-Agent передаем значение «T-Rex_11092008» и получаем флаг! 

Dice

Задание

Наберите 6x6x6 в костях! Сыграть в игру

Формат флага: slcctf{} 

Переходим по активой ссылке http://dice.slcctf.fun/play и нажимаем на кнопку Бросить кости. Значение 6x6x6 не выпадает. Идем в исходный код страницы: нажимаем ПКМ на кнопку Бросить костиИсследовать. Видим, что к кнопке привязано событие, для которого срабатывает следующий JavaScript-код:

Рассмотрим код подробнее:

 document.getElementById('rollButton').addEventListener('click', function() {

            let diceValues = [];

            for (let i = 0; i < 3; i++) {

                let value = Math.floor(Math.random() * 6) + 1;

                if (i == 1 && value == 6) {

                    value = 3;

                }

                diceValues.push(value);

            }                        

            document.getElementById('dice1').src = "../static/images/dice_" + `${diceValues[0]}` + ".png";

            document.getElementById('dice2').src = "../static/images/dice_" + `${diceValues[1]}` + ".png";

            document.getElementById('dice3').src = "../static/images/dice_" + `${diceValues[2]}` + ".png";

            console.log(diceValues[0])

            if (diceValues[0] === 6 && diceValues[1] === 6 && diceValues[2] === 6) {                

                document.getElementById('flag').innerText = 'slcctf{' + btoa((666 ** 666)).match(/.{1,3}/g).join('-').repeat(2) + '}';

            }

        });

В условии видим, что значение второго кубика никогда не будет равно шести. При выпадении шестерки значение всегда меняется на три.

if (i == 1 && value == 6) {

                    value = 3;

                }

Меняем исходный код, чтобы обойти ограничение. Посмотрим, что произойдет, если выпадут три шестерки:

if (diceValues[0] === 6 && diceValues[1] === 6 && diceValues[2] === 6) {                

                document.getElementById('flag').innerText = 'slcctf{' + btoa((666 ** 666)).match(/.{1,3}/g).join('-').repeat(2) + '}';

            }

        });

Чтобы получить флаг, выполняем JavaScript-кода в консоли браузера:

>> 'slcctf{' + btoa((666 ** 666)).match(/.{1,3}/g).join('-').repeat(2) + '}'

"slcctf{SW5-maW-5pd-Hk=SW5-maW-5pd-Hk=}"

Готово! Получаем флаг.

Geo

Задание

日本語を話してください. Формат флага: slcctf{} 

В задании видим японские символы. Идем в переводчик, чтобы перевести на русский:

Ранее мы уже решали задание с подменой языка, поэтому в заголовке Accept-Language передаем значение «ja»:

В ответ сервер передает новую фразу на японском и какую-то строку. Переводим на русской и получаем «Добро пожаловать». Очевидно, нас приглашают перейти по ссылке /ZmxhZ2lzdGhlYmFzZTY0b2Z0aGVwbGFjZW5hbWVpbmVuZ2xpc2g.

В ней видим какие-то дробные числа. Можно предположить, что это координаты. Попробуем найти, что там находится:

Координаты указывают на стадион в Японии. С нами сразу начали говорить на японском: в задании упомянули базу, которая находится на стадионе, а бейсбол в Японии очень популярен. Все кажется взаимосвязанным, но местоположение флага пока неясно.

Возвращаемся на предыдущие шаги и пытаемся поработать со ссылкой — декодируем ее из Base64 в ASCII:

Разделяем строку на слова и получаем: «flag is the base64 of the place name in english». Кодируем название стадиона на английском «Hitachinakashi Sogoundo Park» в Base64:

В итоге получаем флаг: slcctf{SGl0YWNoaW5ha2FzaGkgU29nb3VuZG8gUGFyaw==}

Заключение

На этом все! Надеюсь, статья будет полезна тем, кто планирует организовывать подобные мероприятия. А если у вас уже есть опыт организации CTF-турниров в компаниях, поделитесь им в комментариях.

Также хочу сказать спасибо всем участникам, которые посетили конференцию и участвовали в наших активностях. Увидимся через год на Selectel Tech Day 2025!

Конференция Selectel Tech Day 2024.
Конференция Selectel Tech Day 2024.