10 октября мы провели свой первый CTF-турнир в рамках конференции Selectel Tech Day 2024. Участникам предстояло разгадать семь задач по информационной безопасности — например, найти в море сокровище, приготовить блюдо при 256 градусов, набрать 6x6x6 в костях и другое.
Поскольку ИБ — не основной профиль мероприятия, мы решили ограничить количество заданий и сделать их несложными. В тексте рассказываем, как организовали онлайн- и офлайн-стенд для решения тасков и показываем сами задания.
Подготовка к турниру
В этом году мы сосредоточились на четырех направлениях: серверы и оборудование, облачные технологии, информационная безопасность и машинное обучение. Чтобы организовать интересную активность по нашему профилю, при этом не «загрузить» сложными заданиями посетителей, нам нужно было выполнить несколько требований.
- Задания должны варьироваться от очень простых до средних. В первом случае пользователи могут найти решение за один ход, во втором — за два и более.
- Задания должны быть интересны и под силу тем, кто не разбирается в информационной безопасности.
- Задания можно решить без специального инструментария.
Как я упомянул ранее, ИБ — не основной профиль мероприятия, поэтому делать что-то кроме привычных веб-приложений казалось бессмысленным. В качестве инфраструктуры решили использовать продукты Selectel. А чтобы и посетители, и зрители трансляции могли принять участие, организовали два формата турнира: офлайн и онлайн. Расскажем о каждом подробнее.
Онлайн-стенд
Архитектура онлайн-стенда выглядит следующим образом:
В публичном облаке Selectel развернули три виртуальные машины (далее — ВМ) произвольной конфигурации:
На каждой разместили по одному сетевому интерфейсу, установили Nginx и 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.
При необходимости их можно быстро доставить на ВМ:
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-турнира:
Задания с турнира
Все задания доступны на странице мероприятия. Решения предварительно спрятали под спойлером, чтобы вы могли выполнить их самостоятельно. Полученные флаги можно сдать 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-запроса. Нажимаем ПКМ на странице задания → Исследовать → Network → All и перезагружаем страницу. Во вкладке 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!