Search code examples
python-3.xmetasploitpython-2to3http.client

How can I use/authenticate msfrpc with python3.x?


Edited: The code below works, and the changes are commented. As noted, with python3 one must prefix the string literals with a "b" to produce an instance of the byte type instead of a Unicode str type.

I'm trying to use msfrpc (written in Python 2) in Python 3, and I'm encountering authentication errors. The code I'm using is below; see the comments in the code for the changes I made.

The program runs successfully in python2 (when using httplib rather than http.client), with what appears to be the same authentication exchange as seen when using python3.

import msgpack
import http.client #Changed from httplib

class Msfrpc:
  class MsfError(Exception):
    def __init__(self,msg):
      self.msg = msg
    def __str__(self):
      return repr(self.msg)

  class MsfAuthError(MsfError):
    def __init__(self,msg):
      self.msg = msg

  def __init__(self,opts=[]):
    self.host = opts.get('host') or "127.0.0.1"
    self.port = opts.get('port') or 55552
    self.uri = opts.get('uri') or "/api/"
    self.ssl = opts.get('ssl') or False
    self.authenticated = False
    self.token = False
    self.headers = {"Content-type" : "binary/message-pack" }
    if self.ssl:
      self.client = http.client.HTTPSConnection(self.host,self.port)  #Changed httplib -> http.client
    else:
      self.client = http.client.HTTPConnection(self.host,self.port)        #Changed httplib -> http.client

  def encode(self,data):
    return msgpack.packb(data)
  def decode(self,data):
    return msgpack.unpackb(data)

   def call(self,meth,opts = []):
    if meth != "auth.login":
      if not self.authenticated:
        raise self.MsfAuthError("MsfRPC: Not Authenticated")

    if meth != "auth.login":
      opts.insert(0,self.token)

    opts.insert(0,meth)
    params = self.encode(opts)
    self.client.request("POST",self.uri,params,self.headers)
    resp = self.client.getresponse()
    return self.decode(resp.read()) 

  def login(self,user,password):
    ret = self.call('auth.login',[user,password])
    if ret.get(b'result') == b'success': #Added b
      self.authenticated = True
      self.token = ret.get(b'token') #Added b
      return True
    else:
      raise self.MsfAuthError("MsfRPC: Authentication failed")

if __name__ == '__main__':

  # Create a new instance of the Msfrpc client with the default options
  client = Msfrpc({})

  # Login to the msfmsg server using the password "abc123"
  client.login('msf','abc123')

  # Get a list of the exploits from the server
  mod = client.call('module.exploits')

  # Grab the first item from the modules value of the returned dict
  print ("Compatible payloads for : %s\n" % mod[b'modules'][0]) #Added () #Added b

  # Get the list of compatible payloads for the first option
  ret = client.call('module.compatible_payloads',[mod[b'modules'][0]]) #Added b
  for i in (ret.get(b'payloads')): #Added b
    print ("\t%s" % i)  #Added ()

When running the program, the result is:

root@kali:~/Dropbox/PythonStuff/python-nmap-test# python3 test3.py
Traceback (most recent call last):
  File "test3.py", line 20, in <module>
    client.login('msf','abc123')
  File "/usr/local/lib/python3.7/dist-packages/msfrpc.py", line 64, in login  
    raise self.MsfAuthError("MsfRPC: Authentication failed")
msfrpc.MsfAuthError: 'MsfRPC: Authentication failed'

Curiously, running tcpdump while the program is executing shows a successful authentication and a token being granted:

enter image description here

When the program executes successfully with python2 the authentication exchange appears identical, but if someone feels posting the packet capture of the program (running in python2) completing successfully would help to answer the question I can easily add it.


Solution

  • There wasn't much interest in this but I'm posting the answer should anyone else have the question; the solution was pretty straightforward and I noted the changes in the code posted above.

    The problem was that msfrpc was originally written in Python 2, and therefore the prefix of 'b' in the msgpack RPC responses from the Metasploit host was ignored but it is required in Python 3 to indicate that the literal should become a bytes literal instead of a string type.

    For more information, check out the link below; once I read it the answer was pretty obvious: https://timothybramlett.com/Strings_Bytes_and_Unicode_in_Python_2_and_3.html

    The above code works fine, but really a better solution is to use the msfrpc module written by Dan McInerney which instead uses the 'requests' package.

    https://github.com/DanMcInerney/msfrpc