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

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

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

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

Характеристики облачных серверов.
На каждой разместили по одному сетевому интерфейсу, установили Nginx и 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.
При необходимости их можно быстро доставить на ВМ:
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:
В результате получаем флаг:
slcctf{a90a48277571ea31ff54c0dee577c00077dea703160f7c9464e4469d2724edcf}

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

Далее внимательно читаем описание задания: «Подвалы всегда окутаны загадками и тайнами. Часто они служат не только хранилищем для старых вещей, но и местом для путешествий в мир неизведанного. Надо лишь быть внима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/ и вводим найденный токен:

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

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


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

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

Возвращаемся в консоль разработчика. В ответе сервера нажимаем ПКМ → 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» и получаем флаг!


Отлично, теперь необходимо передать серверу дату и время. На этот раз используем 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-код:

Рассмотрим код подробнее:
В условии видим, что значение второго кубика никогда не будет равно шести. При выпадении шестерки значение всегда меняется на три.
Меняем исходный код, чтобы обойти ограничение. Посмотрим, что произойдет, если выпадут три шестерки:
Чтобы получить флаг, выполняем 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==}

Ранее мы уже решали задание с подменой языка, поэтому в заголовке 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.