iCTF (вдруг кто не в курсе) — одни из самых старых, престижных и уж точно самые экспериментальные командные соревнования по информационной безопасности формата Attack-Defence CTF. Наша команда (Bushwhackers) в этих соревнованиях участвовала и даже, прямо скажем, немножко победила. Полный отчет о мероприятии — в этой статье. Поехали!

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

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

  • виртуальные машины команд-участников размещались на серверах организаторов, причем у команд не было root-доступа к своим машинам;
  • прямого доступа к сервисам других команд не было; эксплойты в виде исходного кода на Python отправлялись организаторам, которые сами их запускали в каждом раунде по хитрым правилам; подобный формат, например, делает невозможными такие финты: «залить через уязвимость в сервисе бэкдор и потом через него собирать флаги даже тогда, когда сервис будет пропатчен», «каждый раунд как можно скорее собирать флаги, после чего их удалять или изменять, чтобы другие команды не набрали очки»;
  • игровые очки, количество которых к концу игры определяло занятое место, начислялись только за защиту своих сервисов, причем защищаться можно было как при помощи патчинга сервисов, так и при помощи мониторинга атак и своевременного сообщения о них; другими словами, команда с непропатченными сервисами, но обнаруживающая 100% атак, не отличается от команды со всеми пропатченными сервисами;
  • за успешную эксплуатацию сервисов командам начислялась игровая валюта, которая не конвертировалась в очки напрямую, но использовалась на аукционе для покупки всяческих ништяков: иммунитета к атакам на следующий раунд, предотвращения выполнения эксплойтов другой команды, получения готового эксплойта для выбранного сервиса и прочего;
  • вся игра была разделена на четыре этапа, которые проходились командами последовательно, причем финальный результат команды определялся только количеством очков, набранных за последний этап.

 

От редакции

Парни скрываются, не подписываются и скромничают настолько, что все фразы их победе в конкурсе в статью дописали редакторы ][ :). А мы не постесняемся и скажем: сборной команде факультета ВМК МГУ имени М. В. Ломоносова, созданной Денисом Гамаюновым и Андреем Петуховым (кстати, нашим старым автором), — воистину респект!

Управление процессом и тактика

Внимательное чтение правил выявило существенный перекос в сторону защиты. Это в значительной мере определило стратегию — большое внимание уделяем патчингу и мониторингу, тем более что из-за отсутствия прямого сетевого доступа между командами количество трафика достаточно невелико (фактически это запросы бота, выставляющего и проверяющего флаги, и эксплойты).

Анализ наших предыдущих игр показал, что главное, чего не хватало команде (не считая убер-скиллов, которых много никогда не бывает), — это нормально организованное взаимодействие участников. Необходимо было следить за тем, кто какой сервис ковыряет, какой прогресс, не подвисла ли какая-нибудь задача и так далее. С самого начала игры мне как капитану стало понятно, что эта функция будет отнимать все мое время, — на сервисы придется забить. Было обидно практически не принимать участия во всеобщем «вонзалове», но в целом этот подход себя оправдал. Зато в каждый отрезок времени было четкое понимание, кто чем занимается, какой прогресс у каждого сервиса.

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

Ставка на мониторинг также принесла результат — внимательное исследование входящего трафика позволило вынуть оттуда несколько эксплойтов, которые мы затем использовали в своих целях. Такая вот разведка.

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

Затем началась аукционная гонка между командами-лидерами, каждая из которых стремилась либо купить иммунитет, либо лишить конкурента возможности атаковать. К примеру, если в начале аукционов мы покупали иммунитет на раунд примерно за пять игровых денег, то к концу игры ставки уже были в районе двухсот. При этом многие команды использовали для игры на аукционе скрипты. К примеру, ставили на один больше, чем текущая максимальная ставка, за пять секунд до конца раунда. Поняв это, мы несколько раз развели топовые команды на большие ставки — нет ничего слаще, чем наблюдать, как твой преследователь раз за разом ставит заоблачные суммы, лишаясь таким образом шансов на успешное продолжение борьбы.

Мы предполагали, что основная борьба среди топ-5 команд закончится примерно минут за сорок до конца игры, когда эти команды потратят все свои деньги, а основная борьба на аукционе развернется между командами, воюющими за места в первой десятке. Так и произошло — ставки на аукционе стали настолько высокими, что командам из топа, потратившим деньги на борьбу в середине четвертого этапа, оставалось только молча наблюдать за таблицей результатов. Достаточное количество игровой валюты и грамотная игра на аукционе позволили нам весь последний раунд держаться впереди и в итоге завоевать первое место с комфортным отрывом по очкам от преследователей.

Разбор полетов

Мы решили описать логику решения не всех, а четырех заданий. Нам показалось, что эта логика будет полезна в контексте того, как имеет смысл думать, встречая аналогичные задачи на CTF.

 

Сервис temperature

У сервиса две команды: запомнить тройку (дата, место, температура) и по паре (дата, место) вернуть температуру. Температура — это флаг, дата — ID флага (ID принимает эксплойт в качестве аргумента), а место — это своеобразный пароль, без знания которого невозможно достать флаг из пропатченного сервиса.
Открываем исходник и видим, что данные хранятся рядом в каталоге, в текстовом файлике neverguess, причем вставляются в него и вытаскиваются оттуда двумя командами: cat neverguess | grep %s | grep %s | awk '{print $3}' для выдачи флагов и echo " %s %s %s " >> neverguess для сохранения. В первую команду подставляется пара (дата, место), а во вторую — тройка (дата, место, температура-флаг).

Так как входные данные никак не фильтруются и не экранируются, сервис уязвим к классической атаке OS command injection. Эх, жаль инфраструктура iCTF не позволяла получать флаги для других сервисов через эту уязвимость. Тогда мы подумали, что эксплойт для этого сервиса можно сделать хитрее. Действительно, допустим, какая-то команда «пропатчит» сервис, добавив кавычки вокруг %s и экранирование спецсимволов shell’а в самой строке. Тогда классическая command injection не прокатит.

Тем не менее флаг все еще легко получить, так как команда grep ищет подстроку во всей строчке, а не только в конкретном поле (она даже не знает, что строка должна быть разбита на какие-то поля). Итак, в наш эксплойт передают ID флага, который надо вытащить (это дата), место мы не знаем. Подключаемся к сервису и запрашиваем данные таким образом: передаем ID флага в качестве даты и строку FLG в качестве места. Так как строка FLG является подстрокой любого флага, а потому точно содержится в целевой строке, мы получим нужный результат.

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

 

Сервис secretvault

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

Использовав strings на бинарнике, по типичным названиям импортируемых функций можно было понять, что исполняемый файл как-то связан с языком Python. Проанализировав тщательнее, находим, что при старте распаковывается некий ZIP-архив, после чего запускается интерпретатор питона. Поскольку архива в папке не было видно, предположили, что он, вероятно, внутри бинарника. Далее по сигнатуре PK\x03\x04 находим начало нужного нам ZIP-архива. В архиве было полно .pyc-файлов, к которым, конечно, тут же был применен uncompile. В конце концов мы получили исходник __main__.pyc.

В результате анализа исходника стало ясно, что сервис поддерживает несколько команд, и среди них есть write, которая записывает строчку в файл типа <имя пользователя>/secret_of_<имя пользователя>.txt, и recv secret, которая возвращает содержимое файла. Имя пользователя — это ID флага, а содержимое файла — сам флаг. Цель атаки — представиться другим пользователем, ID которого передали в наш эксплойт.

Что же, изучаем процесс авторизации и генерации куки. При авторизации пользователя, который уже хоть раз входил в систему, пароль проверяется, так что тривиальные способы отпадают. Имя пользователя может состоять только из букв и цифр. Запоминаем это и смотрим на процесс генерации куки. Оказывается, что кука — это base64(AES(s)), где ключ шифрования нам неизвестен, а s — строка, состоящая из даты, времени, имени пользователя и константы CHALLENGE, которые разделены символом вертикальной черты (|).

Так как имя пользователя обязано состоять из букв и цифр, заставить сервис перепутать поля в куке не получится. Обращаем внимание на то, что s перед шифрованием странным образом дополняется пробелами слева так, что имя пользователя всегда идет с 32-й позиции. Кроме этого, AES используется в режиме ECB. Это значит, что блоки по 16 байт шифруются независимо. Таким образом, если мы получим шифротекст для строки s_1 s_2 s_3 s_4, где s_i — строки, длина которых равна 16, то мы также можем узнать шифротекст для строки s_1 s_2 s_4, для чего достаточно будет выкинуть из шифротекста байты 32–47.

Тогда алгоритм эксплойта получается такой: допустим, нам нужно узнать секрет пользователяuser. Мы получаем куку для пользователя <16 любых букв>user, после чего выбрасываем из полученной куки третий блок из 16 байт и с такой кукой запрашиваем секрет.

Для патча можно, например, вместо имени пользователя писать в куку SHA-1-хеш от него — тогда длина строки будет всегда одинаковой и атака не пройдет.

 

Сервис dexware

Сервис представлял собой «ну очень секретную систему редактирования формул». Помимо стандартных функций типа добавления/удаления/перечисления формул, в меню сервиса была необычная опция upgrade, которой, впрочем, воспользоваться в режиме black box не получилось. Как водится, идем смотреть исходники.

Вместо исходников разработчики задания приготовили DEX-приложение для виртуальной машины Dalvik (то есть Java-приложение, собранное для андроида). После обработки напильником в виде androguard был получен исходный код сервиса на Java, перемежающийся дизассемблерными листингами smali.

Дальнейшее было делом техники — реверс-инжиниринг логики сервиса показал, что функция update загружает с диска JAR-файл и передает управление одной из его функций. Стало быть, к сервису надо подложить JAR-файл с нашим вредоносным классом, вынимающим флаг. Но как это сделать?

Внимательное изучение кода, отвечающего за обработку меню, показало, что помимо отображаемых пунктов меню есть еще скрытый пункт, который активируется вводом числа 42. Изучение обработчика этого пункта показало, что после ввода числа приложение ожидает ввод имени файла, его длину, а затем hex-кодированное содержимое.

Таким образом, эксплуатация состоит в загрузке через скрытый пункт меню JAR-файла и дальнейшей его активации при помощи пункта upgrade.
Поскольку патчить байт-код на smali нам не захотелось, решили получать очки за защиту через мониторинг. Осталось просто сообщать об атаках при получении соединений, активирующих пункт меню 42.

 

Сервис Uranus

Задание представляло собой сервис на Node.js, который на вход принимает POST-запрос с объектом в формате JSON. Если у объекта есть только поля flag_id и token, то сервис пытается считать из папки submissions/ файл с именем flag_id + ‘-‘ + token и в случае наличия такого файла вернет первую группу из следующей регулярки: /password = "([0-9a-zA-Z]{16})"/ (то есть сохраненный пароль, он же флаг). Если в объекте есть еще и поле code, то будет вызвана функция сохранения флагов, которая будет описана позже.

Код сервиса был немного обфусцирован. Для первого шага достаточно заменить выделенныйeval (см. рис. 1) на любую функцию вывода, например console.log(), и запустить.

Рис. 1. Первая итерация по деобфускации кода
Рис. 1. Первая итерация по деобфускации кода

Полученный в результате код обфусцирован с использованием двух приемов. Во-первых, часть выражений заменена на тернарный оператор с константным условием: вызывается функция r3A8, которая, видимо в качестве «пасхалки», осуществляет проверку текущего времени, вследствие чего код сервиса на данный момент уже не работает (для запуска кода можно переопределить метод Date.prototype.getTime, чтобы он возвращал timestamp со времени проведения iCTF). Во-вторых, часть строк заменена на обращения к полям объектаz9o2. Так, например (рис. 2), выглядит «главная» с точки зрения добычи флагов функцияtest.

Рис. 2. Функция test в обфусцированном виде
Рис. 2. Функция test в обфусцированном виде

После обращения описанных приемов обфускации функция test примет следующий вид (рис. 3).

Рис. 3. Функция test после обращения приемов обфускации
Рис. 3. Функция test после обращения приемов обфускации

Данная функция фактически является эквивалентом функции eval за исключением того, что в ней «затерта» переменная global, которая указывает на глобальный контекст выполнения Node.js (на самом деле это ничего особенно не меняет).

Вызывается функция с аргументом b, в котором передается значение поля code JSON-объекта из POST-запроса. Предварительно значение code проверяется на вхождения строк eval, global, Function и this (своего рода фильтрация). В результате вызова test(code) должно быть изменено значение глобальной переменной password. Если после выполнения функции testзначение переменной password стало непустой строкой, то значение code будет записано в файл с именем flag_id + ‘-‘ + token. В ответ сервера же попадет только константная строка: 'Code approved. Please check your parameters carefully before deploying the code to the nuclear reactor.'. Соответственно, проверяющая система устанавливает флаг очень просто: для некоторого значения flag_id и токена в поле code передается строкаpassword = <значение>: в результате выполнения этой строки, во-первых, изменится значение глобальной переменной, а во-вторых, эта же строка будет записана в соответствующий файл.

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

Рис. 4. Логика обработки исключений
Рис. 4. Логика обработки исключений

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

ff.readdirSync('submissions/').forEach(function(file){  
    if (/FLAG_ID/.test(file)) {
        throw ff.readFileSync('submissions/'+file);
    }
})

FLAG_ID — строка, получаемая от системы, запускающей эксплойты, а переменная ffопределена в коде сервиса и является ссылкой на модуль fs для работы с файловой системой.
Фикс сервиса был простой — замена в функции test вызова runInThisContext наrunInNewContext с передачей в качестве контекста объекта с полем password, равным пустой строке.

Рис. 5. Исправления в коде сервиса
Рис. 5. Исправления в коде сервиса
 

Заключение

Мнения в CTF-сообществе о данном мероприятии практически всегда полярны: с одной стороны, многим iCTF очень нравится, с другой стороны, есть товарищи, которые чуть ли не готовы объявить священную войну университету в Санта-Барбаре. Верно одно: iCTF никого не оставляет равнодушным. В заключение хочется пожелать всему CTF-сообществу будущих побед и, самое главное, положительной эмоциональной отдачи от игр, ведь в конечном счете все делается ради этого — изрядно повеселиться.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    4 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии