From bfb5e319bfc580772e114f0b47e10f573ed7bcf4 Mon Sep 17 00:00:00 2001 From: frizb Date: Tue, 13 Jun 2017 17:48:59 -0600 Subject: [PATCH] Vanquish 0.7 Mapping of NMAP XML Command execution managed by the ini files Commands organized by phase --- Vanquish2.py | 222 ++++++++++++++++++++++++++++++++----------------- attackplan.ini | 34 ++++++++ config.ini | 70 ++++++++++++++-- 3 files changed, 242 insertions(+), 84 deletions(-) create mode 100644 attackplan.ini diff --git a/Vanquish2.py b/Vanquish2.py index 60155c7..98fd966 100644 --- a/Vanquish2.py +++ b/Vanquish2.py @@ -32,8 +32,8 @@ Main application logic and automation functions """ -__version__ = '0.1' -__lastupdated__ = 'May 8, 2017' +__version__ = '0.6' +__lastupdated__ = 'June 11, 2017' ### # Imports @@ -45,9 +45,12 @@ import ConfigParser import argparse import random +from pprint import pformat import subprocess +from subprocess import call import multiprocessing - +import xml.etree.ElementTree as ET +from multiprocessing.dummy import Pool as ThreadPool class logger: DEBUG = False; @@ -70,96 +73,158 @@ def __init__(self, argv): print(" Use the -h parameter for help.") self.parser = argparse.ArgumentParser( description='Root2Boot automation platform designed to systematically enumernate and exploit using the law of diminishing returns.') - self.parser.add_argument("-outputFolder", metavar='folder', type=str, nargs=1, default="output", + self.parser.add_argument("-outputFolder", metavar='folder', type=str, default= "."+os.path.sep+"output", help='output folder path (default: %(default)s)') - self.parser.add_argument("-configFile", metavar='file', type=str, nargs=1, default="config.ini", + self.parser.add_argument("-configFile", metavar='file', type=str, default="config.ini", help='configuration ini file (default: %(default)s)') - self.parser.add_argument("-attackPlanFile", metavar='file', type=str, nargs=1, default="attackplan.ini", + self.parser.add_argument("-attackPlanFile", metavar='file', type=str, default="attackplan.ini", help='attack plan ini file (default: %(default)s)') - self.parser.add_argument("-hostFile", metavar='file', type=argparse.FileType("r"), nargs=1, default="hosts.txt", + self.parser.add_argument("-hostFile", metavar='file', type=argparse.FileType("r"), default="hosts.txt", help='list of hosts to attack (default: %(default)s)') + self.parser.add_argument("-domain", metavar='domain', type=str, default="thinc.local", + help='domain to use in DNS enumeration (default: %(default)s)') + self.parser.add_argument("-reportFile", metavar='report', type=str, default="report.txt", + help='filename used for the report (default: %(default)s)') self.parser.add_argument("-resume", action='store_true', help='resume a previous session') self.parser.add_argument("-range", metavar='IPs', type=str, nargs="+", default="", help='a range to scan ex: 10.10.10.0/24') + self.parser.add_argument("-threadPool", metavar='threads', type=int, default="16", + help='Thread Pool Size (default: %(default)s)') + self.parser.add_argument("-verbose", action='store_true', help='display verbose details during the scan') + self.parser.add_argument("-debug", action='store_true', help='display debug details during the scan') self.args = self.parser.parse_args() - self.hosts = self.args.hostFile.readlines() #List of hosts + #self.hosts = self.args.hostFile.readlines() # List of hosts + self.hosts = self.args.hostFile # load config self.config = ConfigParser.ConfigParser() self.config.read(self.args.configFile) - logger.VERBOSE = self.config.getboolean("System","Verbose") - logger.DEBUG = self.config.getboolean("System", "Debug") + + logger.VERBOSE = ( self.config.getboolean("System","Verbose") or self.args.verbose) + logger.DEBUG = (self.config.getboolean("System", "Debug") or self.args.debug) + # load attack plan self.plan = ConfigParser.ConfigParser() self.plan.read(self.args.attackPlanFile) - def scan_hosts(self): - logger.debug("scan_hosts()") - for host in self.hosts: - tcp_services, udp_services = self.scanHost(host) - for service in tcp_services: - port = service[0].split('/')[0] - serv = service[2] - - if serv == 'ssh': - self.addProcess(self.sshEnum, [host, port]) - elif serv == 'ftp': - self.addProcess(self.ftpEnum, [host, port]) - elif serv == 'dns': - self.addProcess(self.dnsEnum, [host, port]) - elif 'https' in serv or 'http' in serv: - protocol = 'http' - if 'ssl' in serv or 'https' in serv: - protocol = 'http' - self.addProcess(self.httpEnum, [host, port, protocol]) - elif serv == 'msSql' or serv == 'ms-sql-s' or serv == 'ms-sql': - self.addProcess(self.msSqlEnum, [host, port]) - elif serv == 'smtp': - self.addProcess(self.smtpEnum, [host, port]) - elif serv == 'snmp': - self.addProcess(self.snmpEnum, [host, port]) - elif serv == 'smb': - self.addProcess(self.smbEnum, [host, port]) - elif serv == 'rdp' or serv == ' microsoft-rdp' or serv == 'ms-wbt-server' or serv == 'ms-term-serv': - self.addProcess(self.rdpEnum, [host, port]) - else: - print "INFO: no module found for %s" % (serv) - - print "INFO: TCP/UDP Nmap scans completed for " + host - - def scanHost(self, host): - logger.debug("INFO: Running general TCP/UDP nmap scans for " + host) - output = "{}/{}".format(self.args.outputFolder, str(host)) - nmap = self.prepare_command("Nmap",{'output':output,'target':host}) - logger.debug( nmap ) - results = subprocess.check_output(nmap, shell=True) - - return self.getInterestingTCP(host, results), self.getInterestingUDP(host, results) - - def getInterestingTCP(self, host, results): - tcpServices=[] - for line in iter(results.splitlines()): - words=line.split() - try: - if words and words[1] == "open" and "/tcp" in words[0]: - tcpServices.append(words) - except: - #weird formatting... - continue - return tcpServices - - #Could implement - def getInterestingUDP(self,host, results): - return [] + #Master NMAP Data Structure Dict + self.nmap_dict = {} + + #current enumeration phase command que + self.phase_commands = [] + + # Enumerate a phase + # phases are defined in attackplan.ini + # enumerate will create a que of all the commands to run in a phase + # then it will create a progress bar and execute a specified number of threads at the same time + # until all the threads are finished then the results are parsed by another function + def enumerate(self,phase_name): + logger.debug("Enumerate - "+ phase_name) + for host in self.nmap_dict: + logger.debug("enumerate() - Host: " + host) + for service in self.nmap_dict[host]['ports']: + logger.debug("\tenumerate() - Service: " + str(service)) + # Look for any known server ports from config file + for known_service, ports in self.config.items('Service Ports'): + if service['name'].find(known_service) <> -1 or service['portid'] in ports.split(','): + logger.debug("\t\tenumerate() - Ports Match: " + ports) + logger.debug("\t\tenumerate() - Known Service: " + known_service) + logger.debug("\t\tenumerate() - Service Port: " + service['portid']) + logger.debug("\t\tenumerate() - Service: " + service['name']) + if (self.plan.has_option(phase_name,known_service)): + for command in self.plan.get(phase_name,known_service).split(','): + command_keys = { + 'output': self.get_scan_path(host, service['name'], command), + 'target': host, + 'domain': self.args.domain, + 'service': service['name'], + 'port':service['portid'] + } + self.phase_commands.append(self.prepare_command(command,command_keys)) + else: + logger.debug("\tenumerate() - NO command section found for phase: " + phase_name + " service name: "+known_service ) + + def get_scan_path(self, host, service, command): + service_path = os.path.join(self.args.outputFolder, service) + # Check folder for existing service + if not os.path.exists(service_path): + os.makedirs(service_path) + return os.path.join(service_path, command+"_" + host.strip().replace(".", "_") + ".xml") + + def scan_hosts(self, hosts): + pool = ThreadPool(self.args.threadPool) + results = pool.map(self.execute_command, hosts) + pool.close() + pool.join() + print results + + def execute_command(self,host): + nmap = self.prepare_command("Nmap", {'output': self.get_scan_path(host, "Nmap"), 'target': host.strip()}) + logger.debug("scan_hosts() - " + "Nmap" + " Command: " + nmap) + stream = os.popen(nmap) def prepare_command(self, command, keyvalues): - command = self.config.get(command,"command") - logger.verbose("command: "+ command) + command = self.config.get(command, "command") + logger.verbose("command: " + command) for k in keyvalues.iterkeys(): logger.verbose(" key: " + k) - command = command.replace("<"+k+">", keyvalues[k]) + command = command.replace("<" + k + ">", keyvalues[k]) return command + def parse_nmap_xml(self, hosts, command): + # NMAP XML Magic Elements + port_attribs_to_read = ['protocol', 'portid'] + service_attribs_to_read = ['name', 'product', 'version', 'hostname', 'extrainfo'] + state_attribs_to_read = ['state'] + xml_nmap_elements = {'service': service_attribs_to_read, 'state': state_attribs_to_read} + searchAddress = {'path': 'address', 'el': 'addr'} + searchPorts = {'path': 'ports', 'el': 'portid'} + nmap_host_element = 'host' + nmap_port_element = 'port' + logger.debug("XML HOSTS " + str(hosts)) + for host in hosts: + # Read a list of addresses and ports + nmap_file = self.get_scan_path(host,"Nmap",command) + if not os.path.isfile(nmap_file): + logger.debug("ERROR NMAP XML PARSE: file not found:" + nmap_file) + continue + logger.debug("XML PARSE: " + nmap_file) + tree = ET.parse(nmap_file) + root = tree.getroot() + for i in root.iter(nmap_host_element): + e = i.find(searchAddress['path']) + find_ports = i.find(searchPorts['path']) + if find_ports is not None: + logger.verbose("NMAP XML PARSE: - Found Address " + e.get(searchAddress['el'])) + addr = e.get(searchAddress['el']) + self.nmap_dict[addr] = {} + #self.nmap_dict[addr]['ip'] = host.strip() + portarray = [] + for port in find_ports.iter(nmap_port_element): + element_dict = {} + self.xml_to_dict(port_attribs_to_read, port, element_dict) + for xml_element in xml_nmap_elements: + for attribute in port.iter(xml_element): + if attribute is not None: + self.xml_to_dict(service_attribs_to_read, attribute, element_dict) + if attribute.get('hostname', '') is not '': + self.nmap_dict[addr]['hostname'] = attribute.get('hostname', '') + + portarray.append(element_dict) + self.nmap_dict[addr]['ports'] = portarray + logger.verbose("NMAP XML PARSE: - Finished NMAP Dict Creation:\n " + str(self.nmap_dict)) + + def xml_to_dict(self, list_to_read, xml_elements, dict): + for element in list_to_read: + dict[element] = xml_elements.get(element, '') + return dict + + def write_report_file(self, data): + report_path = os.path.join(self.args.outputFolder, self.args.reportFile) + f = open(report_path, 'w') + f.write(pformat(data, indent=4, width=1)) + f.close() + def banner(self): banner_numbers = [1, 2, 3] banners = { @@ -218,13 +283,16 @@ def main(self): #open xml logger.verbose("Hosts:"+str(self.hosts)) - #### -- REMOVE ME! - exit() + # Inital NMAP Port Scan + self.hosts = self.hosts.readlines() + #self.scan_hosts(self.hosts) + self.write_report_file(self.nmap_dict) + self.parse_nmap_xml(self.hosts, "Nmap") + self.write_report_file(self.nmap_dict) + self.enumerate("Information Gathering") + self.write_report_file(self.nmap_dict) - if len(self.hosts) > 0: - self.scan_hosts() - else: - self.massScanHosts() + print str(self.phase_commands) logger.verbose("Goodbye!") return 0 diff --git a/attackplan.ini b/attackplan.ini new file mode 100644 index 0000000..3fbc218 --- /dev/null +++ b/attackplan.ini @@ -0,0 +1,34 @@ +#= Vanquish Attack Plan Config File ============ +# Each section represents a phase of the assessment cycle +# the values under each section represent the commands that will be run against each identified service +# the commands are configured in the config.ini file +[Information Gathering] +http: +https: +ftp: FTP Nmap Anon,FTP Nmap Bounce +[Content Enumeration] +http: +https: +ftp: +[Vulnerablity Analysis] +http: +https: +ftp: FTP Nmap All +[Vulnerability Validation] +http: +https: +ftp: +[Brute Forcing] +http: +https: +ftp: FTP Hydra +# use any credentials discovered to execute exploits +[Exploitation] +http: +https: +ftp: +[Exploit Searching] +http: +https: +ftp: + diff --git a/config.ini b/config.ini index b39139d..ed8aad7 100644 --- a/config.ini +++ b/config.ini @@ -2,11 +2,67 @@ [System] Debug: 1 Verbose: 1 -[Masscan] -command: masscan --banner --rate 10000 -p1-1024,1434,1900,4500,49152,3389,1812,8080,2049,3306,5900,1025,8888,4444,31337,2000,8443,8000,9050,8008,3130 -e tap0 --router-ip 10.11.0.1 -oX +[Service Ports] +http: 80,8080,8081,8000,8008,8180,8888,500 +https: 443,8443,9443 +ftp: 21 +telnet: 23 +ssh: 22 +msrpc: 135 +netbios-ssn: 139 +msrpc: 135 +microsoft-ds: 445 +smb: 445 +wsdapi: 5357 +dns: +snmp: +smtp: +rdp: +mysql: +ms-sql: +# Commands ============================== +# The following INI sections are enumeration commands which have the following dynamic replacement values +# = IP Address that the command will be run against +# = Path to the output file specific to this command +# = Port number of this service +# = Service name identified by Nmap +# = Domain specified by the command line parameter -domain +# = Username that was discovered for this host and service +# = Password that was discovered for this host and service + +#= Fast Commands ==================== +# The following commands can be quickly run within a few seconds [Nmap] -command: nmap -sU -sS -F -A -T4 -oN -[Masscan Comprehensive] -command: masscan --banner --rate 10000 -p0-65535 -e tap0 --router-ip 10.11.0.1 -oX -[Nmap Comprehensive] -command: nmap -sS -sU -T4 -A -v -PE -PP -PS80,443 -PA3389 -PU40125 -PY -g 53 --script "default or (discovery and safe)" \ No newline at end of file +Command: nmap -sV --version-all -F -oN .nmap -oX +[DNS Hostname] +Command: nmblookup -A | grep '<00>' | grep -v '' | cut -d' ' -f1 >> +[DNS Zone Transfer] +Command: dig @ -axfr >> +[SMB Nmap Scan] +Command: nmap -v -p --script=smb-check-vulns --script-args=unsafe=1 +[FTP Nmap Anon] +Command: nmap -v -p --script=ftp-anon --script-args=unsafe=1 +Regex: /anon exists/ +Finding: Anon FTP Exists +[FTP Nmap Bounce] +Command: nmap -v -p --script=ftp-bounce --script-args=unsafe=1 +[MySQL Nmap Audit] +Command: nmap -v -p --script=ftp-bounce --script-args=unsafe=1 +#= Slow Commands ==================== +# The following commands can take up to 20 minutes to run +[Nmap All Ports] +Command: nmap -sS -sU -T4 -A -v -PE -PP -PS80,443 -PA3389 -PU40125 -PY -g 53 --script "default or (discovery and safe)" +[DNS Recon] +Command: dnsrecon -d -D /usr/share/wordlists/dnsmap.txt -t std --xml +[FTP Nmap All] +Command: nmap -v -p --script="ftp-*" --script-args=unsafe=1 +[SMB Nmap All] +Command: nmap -v -p --script=smb-* --script-args=unsafe=1 +[MS-SQL Nmap All] +Command: nmap -v -p --script=ms-sql-* --script-args=unsafe=1 +[MySQL Nmap All] +Command: nmap -v -p --script=mysql-* --script-args=unsafe=1 + +#= Exploits ============================= +[MySQL Nmap Audit] +Command: nmap -p --script mysql-audit --script-args "mysql-audit.username='',mysql-audit.password='',mysql-audit.filename='nselib/data/mysql-cis.audit'" \ No newline at end of file