Что скрывается за кулисами CTF-турнира: наш опыт проведения соревнования

zrfqnbvv1vutoggvshpa-s_bcfo.png


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

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

845220a960eddcf58d9741a9ebc8842b.png


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

Используйте навигацию, чтобы выбрать интересующий раздел:

→ Подготовка к турниру
→ Онлайн-стенд
→ Офлайн-стенд
→ Задания с турнира
→ Заключение

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


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

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


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

qxzz_kcgu4uy7jehko8woulyxig.png

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


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

8b467db097b7ce341d98bbc66d720808.png


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

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

8ec07565e9539d43d269dda4666e0db3.png


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

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

52a11a775119a878e0f498eb4452f046.png


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.

9a1e00626bc0a22c85d4612fb80a9b74.png


Задачи в 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.

666ffdf59db06dd80c87c8ea1ca4a4bf.png


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

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

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


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

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

d50cb73bc29303b0d821873f6e84ff86.png


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

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

b616f29572a2007f8038d62082560d06.png


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


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

Deep


Задание

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

7708e6ee0306e4498f625d403ce24a30.png


Перейти к задаче →

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


256 Degrees


Задание

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

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

e1d8dc7319c05ed05fad3b3576b8583a.png


Перейти к задаче →

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

На главной странице задания есть условие, что блюдо должно быть приготовлено на 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{}

ddfdfa64f607585667086fbb3b049dfc.png


Перейти к задаче →

Решение
Нужно найти путь к 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 получаем флаг:

b45370438075344fd0c2849815fb3dba.png


Air


Задание

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

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

5fec8a4d2889dbcc520eb33115cba925.png


Перейти к задаче →

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

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

В поле name указано значение passenger, но нам нужно представиться пилотом. Меняем значение на pilot и получаем новый токен:
0670b113907a3f52c5e9d8b6c5f50c66.png

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


Future


Задание

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

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

8b1275b22c4827ae83bbb9486f56b058.png


Перейти к задаче →

Решение


Dice


Задание

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

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

ec4256e4f7bad75dc9eb0c00769de47c.png


Перейти к задаче →

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

Рассмотрим код подробнее:
  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{}

34259d60d34f1e71ffbb20978c7f9b11.png


Перейти к задаче →

Решение


Заключение


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

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

4333618a5a762ba203fe64cb363e3b57.png


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

© Habrahabr.ru