Skip to content

Commit

Permalink
Vanquish 0.7
Browse files Browse the repository at this point in the history
Mapping of NMAP XML
Command execution managed by the ini files
Commands organized by phase
  • Loading branch information
frizb authored Jun 13, 2017
1 parent cce4313 commit bfb5e31
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 84 deletions.
222 changes: 145 additions & 77 deletions Vanquish2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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 = {
Expand Down Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions attackplan.ini
Original file line number Diff line number Diff line change
@@ -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:

70 changes: 63 additions & 7 deletions config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,67 @@
[System]
Debug: 1
Verbose: 1
[Masscan]
command: masscan <target_range> --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 <output>
[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
# <target> = IP Address that the command will be run against
# <output> = Path to the output file specific to this command
# <port> = Port number of this service
# <service> = Service name identified by Nmap
# <domain> = Domain specified by the command line parameter -domain
# <username>= Username that was discovered for this host and service
# <password>= 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 <target> -oN <output>
[Masscan Comprehensive]
command: masscan <target_range> --banner --rate 10000 -p0-65535 -e tap0 --router-ip 10.11.0.1 -oX <output>
[Nmap Comprehensive]
command: nmap -sS -sU -T4 -A -v -PE -PP -PS80,443 -PA3389 -PU40125 -PY -g 53 --script "default or (discovery and safe)" <target>
Command: nmap -sV --version-all -F <target> -oN <output>.nmap -oX <output>
[DNS Hostname]
Command: nmblookup -A <target> | grep '<00>' | grep -v '<GROUP>' | cut -d' ' -f1 >> <output>
[DNS Zone Transfer]
Command: dig @<target> <domain> -axfr >> <output>
[SMB Nmap Scan]
Command: nmap -v -p <port> --script=smb-check-vulns --script-args=unsafe=1 <target>
[FTP Nmap Anon]
Command: nmap -v -p <port> --script=ftp-anon --script-args=unsafe=1 <target>
Regex: /anon exists/
Finding: Anon FTP Exists
[FTP Nmap Bounce]
Command: nmap -v -p <port> --script=ftp-bounce --script-args=unsafe=1 <target>
[MySQL Nmap Audit]
Command: nmap -v -p <port> --script=ftp-bounce --script-args=unsafe=1 <target>
#= 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)" <target>
[DNS Recon]
Command: dnsrecon -d <target> -D /usr/share/wordlists/dnsmap.txt -t std --xml <output>
[FTP Nmap All]
Command: nmap -v -p <port> --script="ftp-*" --script-args=unsafe=1 <target>
[SMB Nmap All]
Command: nmap -v -p <port> --script=smb-* --script-args=unsafe=1 <target>
[MS-SQL Nmap All]
Command: nmap -v -p <port> --script=ms-sql-* --script-args=unsafe=1 <target>
[MySQL Nmap All]
Command: nmap -v -p <port> --script=mysql-* --script-args=unsafe=1 <target>

#= Exploits =============================
[MySQL Nmap Audit]
Command: nmap -p <port> --script mysql-audit --script-args "mysql-audit.username='<username>',mysql-audit.password='<password>',mysql-audit.filename='nselib/data/mysql-cis.audit'" <target>

0 comments on commit bfb5e31

Please sign in to comment.