Привет, Хабр! Я Ваня, ведущий инженер по информационной безопасности в 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:

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/ и вводим найденный токен:


В поле 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» и получаем флаг!



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.

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


  1. voldemar_d
    26.10.2024 08:00

    CTF-турнир 

    Почему-то первое, что пришло в голову - Capture the flag, только из Quake 2 :)


  1. greg0r0
    26.10.2024 08:00

    Респект за обзор архитектуры сети, это полезно ребятам, которым только предстоит делать свои первые CTF-ивенты.

    Но какая же это дичайшая уцуцуга*, так не надо делать

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


    Упущен логический шаг перехода к взятию хэша и с точки зрения участника этот шаг не очевидн. Да и почему именно саша? У нас еще из 256 битовых есть и RIPEMD, и Keccak-256 etc. Да и какой вариант sha используется? sha2 или sha3? И это только что-то из известных.

    Как и:
    > «х» в ASCII русских символов.

    Таблиц кодировок много разных... Опять упущен логически шаг, который приводит к неясности и перебору.



    * Уцуцуга - задание-угадайка в CTF контесте. Обычно возникает т.к. автор пропустил какой-то логический шаг при реализации задачи.


    1. is113 Автор
      26.10.2024 08:00

      Спасибо за комментарий!
      Учтем замечания в следующих турнирах :)


  1. BigDadChill
    26.10.2024 08:00

    10 ноября

    Может быть октября?


    1. is113 Автор
      26.10.2024 08:00

      Да, 10 октября. Спасибо, поправили)