Привет! Это Маша из AppSec Альфа-Банка. В прошлом году мы провели наш первый Alfa CTF Surfing Edition — соревнование в сфере кибербезопасности. Их делают для того, чтобы лучше искать уязвимости, атаковать чужую инфраструктуру или защищать свою. 

Сегодня мы разберем пару тасок из нашего CTF: «Запреты Роскамбалы» и «Звуки ностальгии». Задачи пропитаны work-life blend, послевкусием летнего отпуска и волн.

Приступим.

№1.«Звуки ностальгии»: задача на внимательность и гуглеж

Описание задачи: 

«Вы находите в уголке хижины пыльную коробку с надписью «Не трогать». Внутри — чей-то личный дневник, тетрадь с Tokio Hotel на обложке и тр3-плеер. В приступе ностальгии вы надеваете наушники и нажимаете Plaу... Внезапно мир вокруг начинает меняться, и вы оказываетесь в совершенно незнакомом месте, недоумевая, как сюда попали.

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

  • Чат называется: NETCLUB.20070912.HTM 

  • Задача: Выберитесь из ловушки ностальгии и найдите способ вернуться в настоящее: nostalgia-aticjZp9.alfactf.ru/»

Осматриваемся

В решении любой CTF-задаче важна не только эрудированность, но и умение разбираться в любой ситуации, находить информацию и думать. Поэтому мы изуча��м то, что у нас есть — ссылку и чат. По ссылке мы обнаруживаем MP3 Player и некую карту. 

Помним, что это CTF, а значит, это лишь начало пути (потому что задачи созданы, чтобы копать), поэтому идём читать чат. Изучаем чат так тщательно, насколько можем, ведь неочевидные подсказки могут быть даже в сообщениях, которые не имеют прямого отношения к делу.

Например, сообщения пользователя, который жалуется, что не помнит, как он сюда попал. А из последних воспоминаний остались только те, в которых он нашёл наушники и, надев их, оказался где-то в прошлом, без понимания, как вернуться домой. Может быть это то что нам нужно?

Над ним смеются, но находится пользователь с интересным ником TimeTravel_0, который тоже через такое проходил и настаивает, что нужно найти улицу, где была записана песня, которая его сюда затянула. Наш бедолага спрашивает: «Какая песня? Я даже не помню, что слушал…У меня есть только это фото». Фото оказывается кадром из клипа.

Зацепка

Это зацепка. Вероятнее всего, нам нужно:

  1. Найти точное место, где снимался клип.

  2. Оказавшись на точке, мы включим ту самую песню и так разорвем временную петлю.

Гуглинг, ожидаемо, ничего не даёт. Но так как это задание на внимательность, то мы замечаем сообщения пользователя с ником Alpha Music (а у нас CTF от Альфы), который пишет, что сохраняет треки в свой ЖЖ. 

Ищем ЖЖ пользователя и просматриваем его записи, благо их всего 10. 

В одном из постов видим ту самую фотографию из чата.

Находим клип

Шазамим, находим клип, смотрим. На второй минуте находится кадр с фрагментом нашей фотографии. Значит это именно тот клип и та песня.

Что делать дальше? Изучать информацию, касающуюся произведения, а так как нам нужна точка на карте, то ищем место, где снимался клип.

Но дальнейшее изучение вопроса ни к чему не приводит. Возможно, стоит изучить клип ещё раз клип и поискать зацепки? 

Находим локацию

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

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

Это где-то недалеко от Лос-Анджелеса.

Флаг получен

В итоге у нас есть песня и локация, в которую нам нужно переместиться. Целимся, метимся, перемещаемся по карте именно на те координаты, на которых находится человек на фото. 

Но секрет в том, что нам нужно навестись на здание.

И вот, получилось — мы забираем флаг, задача решена!

Решая эту задачу, нужно было просто внимательно читать чат, сосредоточенно смотреть клип и получше гуглить. В целом всё.

#2. «Запреты Роскамбалы»: уровень medium well

Описание задания:

«Вы прогуливаетесь по острову и внезапно встречаете аборигенов. Странное дело: все они выглядят очень грустными. Оказывается, что местные жители давно не видели новых мемов, а смеяться над «Me gusta» и «Oh stop it, you» уже надоело.

Дело в том, что вредная Роскамбала ограничила аборигенам доступ в интернет: завалилась на трансатлантический кабель и пережала его своим толстым брюхом. Победите Роскамбалу и восстановите свободный доступ к мемам.

Изучаем исходники

Открыв сайт мы видим саму Роскамбалу и её характеристики: 100 ХП и 5 stamina. При этом у нас есть три разных вида оружия: с пятью единицами урона, с десятью и с одной единицей. Каждый раз, когда мы выбираем оружие и наносим удар, тратится одна stamina и вычитается энное количество урона из здоровья. Но даже с самым сильным оружием нам просто не хватает урона, чтобы её добить.

Изучив устройство инфраструктуры, заметим, что пользователи могут обращаться к трём различным серверам: Backend EU, Backend US, Backend SG. Эти сервера взаимодействуют со своими Redis Clusters, которые потом синхронизируются друг с другом. 

Посмотрим исходники Backend: 

  • Используется Redis. 

  • У нас есть только три вида оружия и мы не можем обойти это ограничение.  

  • У нас есть несколько функций для взаимодействия с Redis и основная функция с WebSocket. 

Как всё работает:

  • Есть всего два типа запросов: “new_game” и “attack”, которые могут прилететь на Backend.

  • Когда мы подключаемся к сайту по WebSocket, то создаётся игра. В это время Backend отправляет JSON с ключом “type”: “game_created”, с ключом “game_id”: game_id, с ’’hp’’: 100 и “stamina”: 5.

  • Когда отправляем запрос атаки, то сначала проверяется, что указан верный game_id. Если нет, то отправляется ошибка.

  • Далее проверяется оружие. Если оружие некорректное, сервер отправляет ошибку, и атака не выполняется.

  • Затем сервер получает значения stamina (выносливости) и hp (очков здоровья) игрока из базы данных. Сервер проверяет: если hp меньше или равно нулю, он отправляет JSON-ответ с ключом “gameover”, в котором содержится флаг.

  • Далее сервер проверяет, осталась ли у игрока stamina. Если stamina исчерпана, сервер отправляет JSON с сообщением об окончании игры и предлагает создать новую. Важно: сначала проверяется stamina и HP, а уже потом происходит сама атака.

В бэкенде есть Dockerfile, где указано, что сервер запускается по IP-адресу хоста на порте 8000. Docker Compose перенаправл��ет этот порт 8000 на внешний порт 8101. В файле окружения (environment variables) также виден флаг — в исходниках он зацензурен, поэтому будем извлекать его через exploit.

Как вы понимаете, просто так заспамить Роскамбалу с одного конкретного сервера не получится. Поэтому будем пробовать спамить с трёх: по 5 атак с каждого сервера — это 15 атак в сумме, а после синхронизации (теоретически) мы сможем добить до -50 ХП и получить флаг.

Собственно, план, и реализация

Собственно, exploit состоит в том, что мы используем несколько серверов разных регионов (EU, US, SG), указанных в условии задачи:

  • Берём базовый URL сервера EU из условия и заменяем «EU» на «US» и «SG», получая три адреса: server-EU:8101, server-US:8101, server-SG:8101.

  • Подключаемся ко всем трём по WebSocket.

  • На первом сервере (EU) отправляем запрос на создание новой игры (отправляем только на один, иначе будут созданы три разные игры) и получаем “game_id” из ответа. 

  • Ждём инициализацию игры на бэкенде.

  • Спамим атаками на каждый из серверов: создаём 50 атак на одно соединение, суммарно — 150.

  • Создаём атаку в функции attack, передавая туда соединение и “game_id” по WebSocket.

  • Функция attack отправляет JSON по указанному WebSocket. В JSON указан тип запроса,game_id и оружие, которое наносит больше всего урона. 

  • Затем на все три соединения отправляем по 50 атак с этим же Game ID, чтобы истощить HP и stamina, вызвав условие Gameover с флагом на любом сервере.

Отправляем все 150 атак и ждём, когда придут ответы на все атаки, и получаем результат:

results = await asyncio.gather(*[
asyncio.wait_for(ws.recv(), timeout=0.5) for ws in conns
]) 

После этого у нас должен полететь JSON в котором есть ключ “is_game_over” с флагом:

for r in results:
    data = json.loads(r)
    flag = data.get(“is_game_over”)
    if isinstance(flag, str) and “alfa{” in flag:
        print(flag)
        return

Собственно, это как раз и есть проверка.

Запустив exploit, мы получим результат — нужный нам флаг.

Задача решена!

Если хочешь проверить себя снова — у нас для тебя сюрприз. 🚀 Мы уже анонсировали новый CTF-турнир. Лендинг уже в сети, детали — совсем скоро. Жми на ссылку, занимай стартовую позицию и выигрывай денежные призы!

Решайте задачи, играйте в CTF и делайте безопасно.