├── .project ├── .pydevproject ├── README.md ├── css └── htmlStyles.css └── NMAPgrapher.py /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | NMAPgrapher 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /${PROJECT_DIR_NAME} 5 | 6 | python 2.7 7 | Default 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NMAPgrapher v0.1a 2 | A tool to generate graph and other output from NMAP XML files. 3 | 4 | ### What is it for? 5 | This tool was primarily intended to easily digest NMAP output for inclusion in penetration testing reports as supplementary information, however feel free to use it as you wish. 6 | 7 | ### What does it do? 8 | NMAP grapher is currently capable of generating the following outputs: 9 | - Most and Least Common Services 10 | - Most and Least Common Ports 11 | - Most and Least Common Operating Systems 12 | - Hosts with Most and Least number of open ports 13 | - HTML document with tables including each host and open services / ports 14 | 15 | The following output formats are supported: 16 | - PNG 17 | - SVG 18 | - HTML 19 | - CSV 20 | 21 | ### Where can I find a complete usage guide? 22 | Usage information can be found at http://www.attactics.org/2015/09/nmapgrapher-tool-for-digesting-nmap-xml.html. Even more usage information can be found by running: 23 | ```NMapgrapher.py -h``` 24 | 25 | ### Your code sucks and it's broken 26 | I'm not a python ninja and the tool is in alpha, so that is quite possible. Feel free to contact me on twitter (@evasiv3) and I can fix any bugs or issues you are experiencing. 27 | -------------------------------------------------------------------------------- /css/htmlStyles.css: -------------------------------------------------------------------------------- 1 | .table { 2 | margin:0px;padding:0px; 3 | width:300px; 4 | border:1px solid #000000; 5 | 6 | -moz-border-radius-bottomleft:5px; 7 | -webkit-border-bottom-left-radius:5px; 8 | border-bottom-left-radius:5px; 9 | 10 | -moz-border-radius-bottomright:5px; 11 | -webkit-border-bottom-right-radius:5px; 12 | border-bottom-right-radius:5px; 13 | 14 | -moz-border-radius-topright:5px; 15 | -webkit-border-top-right-radius:5px; 16 | border-top-right-radius:5px; 17 | 18 | -moz-border-radius-topleft:5px; 19 | -webkit-border-top-left-radius:5px; 20 | border-top-left-radius:5px; 21 | }.table table{ 22 | border-collapse: collapse; 23 | border-spacing: 0; 24 | width:100%; 25 | height:100%; 26 | margin:0px;padding:0px; 27 | }.table tr:last-child td:last-child { 28 | -moz-border-radius-bottomright:5px; 29 | -webkit-border-bottom-right-radius:5px; 30 | border-bottom-right-radius:5px; 31 | } 32 | .table table tr:first-child td:first-child { 33 | -moz-border-radius-topleft:5px; 34 | -webkit-border-top-left-radius:5px; 35 | border-top-left-radius:5px; 36 | } 37 | .table table tr:first-child td:last-child { 38 | -moz-border-radius-topright:5px; 39 | -webkit-border-top-right-radius:5px; 40 | border-top-right-radius:5px; 41 | }.table tr:last-child td:first-child{ 42 | -moz-border-radius-bottomleft:5px; 43 | -webkit-border-bottom-left-radius:5px; 44 | border-bottom-left-radius:5px; 45 | }.table tr:hover td{ 46 | 47 | } 48 | .table tr:nth-child(odd){ background-color:#e5e5e5; } 49 | .table tr:nth-child(even) { background-color:#ffffff; }.table td{ 50 | vertical-align:middle; 51 | 52 | 53 | border:1px solid #000000; 54 | border-width:0px 1px 1px 0px; 55 | text-align:center; 56 | padding:7px; 57 | font-size:10px; 58 | font-family:Arial; 59 | font-weight:normal; 60 | color:#000000; 61 | }.table tr:last-child td{ 62 | border-width:0px 1px 0px 0px; 63 | }.table tr td:last-child{ 64 | border-width:0px 0px 1px 0px; 65 | }.table tr:last-child td:last-child{ 66 | border-width:0px 0px 0px 0px; 67 | } 68 | .table tr:first-child td{ 69 | background:-o-linear-gradient(bottom, #cccccc 5%, #b2b2b2 100%); background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #cccccc), color-stop(1, #b2b2b2) ); 70 | background:-moz-linear-gradient( center top, #cccccc 5%, #b2b2b2 100% ); 71 | filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#cccccc", endColorstr="#b2b2b2"); background: -o-linear-gradient(top,#cccccc,b2b2b2); 72 | 73 | background-color:#cccccc; 74 | border:0px solid #000000; 75 | text-align:center; 76 | border-width:0px 0px 1px 1px; 77 | font-size:14px; 78 | font-family:Arial; 79 | font-weight:bold; 80 | color:#000000; 81 | } 82 | .table tr:first-child:hover td{ 83 | background:-o-linear-gradient(bottom, #cccccc 5%, #b2b2b2 100%); background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #cccccc), color-stop(1, #b2b2b2) ); 84 | background:-moz-linear-gradient( center top, #cccccc 5%, #b2b2b2 100% ); 85 | filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#cccccc", endColorstr="#b2b2b2"); background: -o-linear-gradient(top,#cccccc,b2b2b2); 86 | 87 | background-color:#cccccc; 88 | } 89 | .table tr:first-child td:first-child{ 90 | border-width:0px 0px 1px 0px; 91 | } 92 | .table tr:first-child td:last-child{ 93 | border-width:0px 0px 1px 1px; 94 | } 95 | -------------------------------------------------------------------------------- /NMAPgrapher.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | import operator 3 | import csv 4 | import os 5 | import shutil 6 | import sys 7 | import pygal 8 | from pygal.style import Style 9 | import argparse 10 | allhosts = [] 11 | 12 | class bcolors: 13 | HEADER = '\033[95m' 14 | OKBLUE = '\033[94m' 15 | OKGREEN = '\033[92m' 16 | WARNING = '\033[93m' 17 | FAIL = '\033[91m' 18 | ENDC = '\033[0m' 19 | BOLD = '\033[1m' 20 | UNDERLINE = '\033[4m' 21 | 22 | class HostObj(object): 23 | def __init__(self,ipAddr,tcpPorts,udpPorts,services,detailedServices,operSys): 24 | self.ipAddr=ipAddr 25 | self.tcpPorts=tcpPorts 26 | self.udpPorts=udpPorts 27 | self.services=services 28 | self.detailedServices=detailedServices 29 | self.operSys=operSys 30 | 31 | def printBanner(): 32 | banner=""" _ _ __ __ _ ____ _ 33 | | \ | | \/ | / \ | _ \ __ _ _ __ __ _ _ __ | |__ ___ _ __ 34 | | \| | |\/| | / _ \ | |_) / _` | \'__/ _` | \'_ \| \'_ \ / _ \ \'__| 35 | | |\ | | | |/ ___ \| __/ (_| | | | (_| | |_) | | | | __/ | 36 | |_| \_|_| |_/_/ \_\_| \__, |_| \__,_| .__/|_| |_|\___|_| 37 | attactics.org|___/ |_|@evasiv3 v0.1a 38 | | Generates graph, html, and csv data from NMAP XML files | """ 39 | print banner 40 | print '' 41 | 42 | def populateObjects(file): 43 | e = ET.parse(file).getroot() 44 | tcpPortList = [] 45 | udpPortList = [] 46 | detailedServiceList = [] 47 | serviceList = [] 48 | osList = [] 49 | for child in e: 50 | for host in child: 51 | if host.tag == "address": 52 | ipAddr = host.attrib['addr'] 53 | topOS = False 54 | for info in host: 55 | if info.tag =="osmatch": 56 | if info.attrib['name']: 57 | if not topOS: 58 | osList.append(info.attrib['name']) 59 | topOS = True 60 | if info.tag == "port": 61 | if info.attrib['protocol'] == 'tcp': 62 | tcpPortList.append(info.attrib['portid']) 63 | if info.attrib['protocol'] == 'udp': 64 | udpPortList.append(info.attrib['portid']) 65 | for service in info: 66 | if service.tag == "service": 67 | detailedServiceList.append( 68 | info.attrib['protocol']+ 69 | '/'+info.attrib['portid']+' '+ 70 | service.attrib['name']) 71 | serviceList.append(service.attrib['name']) 72 | if tcpPortList or udpPortList: 73 | allhosts.append(HostObj( 74 | ipAddr, 75 | tcpPortList, 76 | udpPortList, 77 | serviceList, 78 | detailedServiceList, 79 | osList)) 80 | tcpPortList = [] 81 | udpPortList = [] 82 | serviceList = [] 83 | detailedServiceList = [] 84 | osList = [] 85 | 86 | def generateHostPortListOutput(hostList,filename): 87 | if not os.path.exists(args.outputBaseName): 88 | os.makedirs(args.outputBaseName) 89 | shutil.copy('css/htmlStyles.css', args.outputBaseName+'/htmlStyles.css') 90 | f=open(args.outputBaseName+'/'+filename,'w+') 91 | f.write('\n\n\n\n\n\n') 92 | for item in hostList: 93 | f.write('
\n\n\n') 94 | f.write('\n') 95 | f.write('\n') 96 | if item[1]: 97 | for tcpPort in item[1]: 98 | f.write('\n') 99 | f.write('') 100 | f.write('\n') 101 | f.write('
\n'+item[0]+'\n
TCP '+str(tcpPort)+'
\n
\n
\n
') 102 | f.write('\n\n') 103 | f.close 104 | 105 | def generateOutput(itemList, title, headers, filename, outputFormat): 106 | if not os.path.exists(args.outputBaseName): 107 | os.makedirs(args.outputBaseName) 108 | if outputFormat == 'html': 109 | shutil.copy('css/htmlStyles.css', args.outputBaseName+'/htmlStyles.css') 110 | f=open(args.outputBaseName+'/'+filename+'.html','w+') 111 | f.write('\n\n\n\n\n\n') 112 | f.write('

\n'+title+'

\n') 113 | f.write('
\n\n\n') 114 | for i in range(len(headers)): 115 | f.write('\n') 116 | f.write('\n') 117 | for item in itemList: 118 | f.write('\n') 119 | for i in range(len(headers)): 120 | f.write('') 121 | f.write('\n') 122 | f.write('
\n'+headers[i]+'\n
'+str(item[i])+'
\n\n') 123 | f.close 124 | elif outputFormat == 'csv': 125 | f=open(args.outputBaseName+'/'+filename,'w+') 126 | wrcsv = csv.writer(f, delimiter=',') 127 | wrcsv.writerow(headers) 128 | for item in itemList: 129 | wrcsv.writerow(item) 130 | f.close 131 | else: 132 | x = [] 133 | y = [] 134 | custom_style = Style( 135 | background='#ffffff', 136 | plot_background='transparent', 137 | foreground='#00000', 138 | font_family='arial', 139 | title_font_family='arial', 140 | title_font_size= 24, 141 | foreground_strong='#00000', 142 | foreground_subtle='#00000', 143 | transition='400ms ease-in', 144 | colors=('#7FAF7F', '#E8537A', '#E95355', '#E87653', '#E89B53')) 145 | bar_chart=pygal.Bar(human_radeable=True, truncate_label=-1, interpolate='cubic', style=custom_style) 146 | for row in itemList: 147 | x.append(row[0]) 148 | y.append(row[1]) 149 | bar_chart.title = title 150 | bar_chart.x_labels = map(str, x) 151 | bar_chart.add('Total', y) 152 | if outputFormat == "png": 153 | fileName = args.outputBaseName+'/'+filename+'.png' 154 | bar_chart.render_to_png(fileName) 155 | if outputFormat == "svg": 156 | fileName = args.outputBaseName+'/'+filename+'.svg' 157 | bar_chart.render_to_file(fileName) 158 | 159 | def getPorts(number, sort, typePort): 160 | allPorts = [] 161 | for host in allhosts: 162 | if typePort == "tcp": 163 | for port in host.tcpPorts: 164 | allPorts.append(port) 165 | if typePort == "udp": 166 | for port in host.udpPorts: 167 | allPorts.append(port) 168 | if typePort == "both": 169 | for port in host.tcpPorts: 170 | allPorts.append(port) 171 | for port in host.udpPorts: 172 | allPorts.append(port) 173 | # Get only unique values 174 | allPortsSet = set(allPorts) 175 | totals = [] 176 | for port in allPortsSet: 177 | list = [] 178 | list.append(port) 179 | list.append(allPorts.count(port)) 180 | totals.append(list) 181 | if sort == "top": 182 | return sorted(totals, key=operator.itemgetter(1),reverse=True)[:number] 183 | else: 184 | return sorted(totals, key=operator.itemgetter(1))[:number] 185 | 186 | def checkArgs(args): 187 | if not os.path.isfile(args.inputFile): 188 | print bcolors.FAIL + ' [!] - Input file cannot be found. Quitting...' + bcolors.ENDC 189 | sys.exit() 190 | if args.tports and args.tports not in ['tcp', 'udp', 'both']: 191 | print bcolors.FAIL + ' [!] - Invalid port type specified. Quitting...' + bcolors.ENDC 192 | sys.exit() 193 | if args.outputFormat not in ['csv','svg','png','html']: 194 | print bcolors.FAIL + ' [!] - Invalid output type specified. Quitting...' + bcolors.ENDC 195 | sys.exit() 196 | if not args.c: 197 | print bcolors.WARNING + '[!] - Custom item count not specified, defaulting to 10.' + bcolors.ENDC 198 | 199 | def getOperSys(number, sort): 200 | allOperSys = [] 201 | for host in allhosts: 202 | for operSys in host.operSys: 203 | allOperSys.append(operSys) 204 | # Get only unique values 205 | allOperSysSet = set(allOperSys) 206 | totals = [] 207 | for operSys in allOperSysSet: 208 | list = [] 209 | list.append(operSys) 210 | list.append(allOperSys.count(operSys)) 211 | totals.append(list) 212 | if sort == "top": 213 | return sorted(totals, key=operator.itemgetter(1),reverse=True)[:number] 214 | else: 215 | return sorted(totals, key=operator.itemgetter(1))[:number] 216 | 217 | def getServices(number, sort): 218 | allServices = [] 219 | for host in allhosts: 220 | for port in host.services: 221 | allServices.append(port) 222 | # Get only unique values 223 | allServicesSet = set(allServices) 224 | totals = [] 225 | for service in allServicesSet: 226 | list = [] 227 | list.append(service) 228 | list.append(allServices.count(service)) 229 | totals.append(list) 230 | if sort == "top": 231 | return sorted(totals, key=operator.itemgetter(1),reverse=True)[:number] 232 | else: 233 | return sorted(totals, key=operator.itemgetter(1))[:number] 234 | 235 | def getHosts(number,sort): 236 | totals = [] 237 | for host in allhosts: 238 | list = [] 239 | list.append(host.ipAddr) 240 | list.append(len(host.tcpPorts)+len(host.udpPorts)) 241 | totals.append(list) 242 | if sort == "top": 243 | return sorted(totals, key=operator.itemgetter(1),reverse=True)[:number] 244 | else: 245 | return sorted(totals, key=operator.itemgetter(1))[:number] 246 | 247 | def getHostPortList(): 248 | list = [] 249 | for host in allhosts: 250 | list.append([host.ipAddr, host.detailedServices]) 251 | return list 252 | 253 | 254 | parser = argparse.ArgumentParser(description='\n * If optional graph flags are not specified: *\n\t - All graphs are outputted\n\t - Port counts will include TCP and UDP\n\t - Item count will default to 10',formatter_class=argparse.RawTextHelpFormatter) 255 | parser.add_argument('inputFile', help='Input file. Options:\n NMAP XML') 256 | parser.add_argument('outputBaseName', help='Output file(s) base name\n') 257 | parser.add_argument('outputFormat', help='Output format: \n svg\n png\n csv\n html') 258 | parser.add_argument('-c', help='Number of items to include in graphs.') 259 | parser.add_argument('-tports', metavar="TYPE", help='Most common ports. Types are:\n tcp\n udp\n both') 260 | parser.add_argument('-bports', metavar="TYPE", help='Least common ports. Types are:\n tcp\n udp\n both') 261 | parser.add_argument('-tservices', help='Most common services.', action="store_true") 262 | parser.add_argument('-bservices', help='Least common services.', action="store_true") 263 | parser.add_argument('-tos', help='Most common operating systems.', action="store_true") 264 | parser.add_argument('-bos', help='Least common operating systems.', action="store_true") 265 | parser.add_argument('-thosts', help='Hosts with most open ports', action="store_true") 266 | parser.add_argument('-bhosts', help='Hosts with least open ports', action="store_true") 267 | parser.add_argument('-hostlist', help='HTML output of all hosts with service / port info', action="store_true") 268 | args = parser.parse_args() 269 | printBanner() 270 | checkArgs(args) 271 | populateObjects(args.inputFile) 272 | 273 | if args.c == None: 274 | args.c = '10' 275 | if (args.tports == None) and (args.bports == None) and (args.hostlist == False) and (args.tservices == False) and (args.bservices == False) and (args.tos == False) and (args.bos == False) and (args.thosts == False) and (args.bhosts == False): 276 | print bcolors.WARNING + '[+] - No output flags specified, generating default outputs in '+args.outputFormat+' format.' + bcolors.ENDC 277 | args.tports = 'both' 278 | args.bports = 'both' 279 | args.tservices = True 280 | args.bservices = True 281 | args.tos = True 282 | args.hostList = True 283 | args.bos = True 284 | args.thosts = True 285 | args.bhosts = True 286 | checkArgs(args) 287 | if args.tports: 288 | res = getPorts(int(args.c),'top',args.tports) 289 | if not res: 290 | print bcolors.WARNING + '[!] - No port info found, related output will not be generated.' + bcolors.ENDC 291 | else: 292 | generateOutput(res, 'Top '+args.c+' Most Common Ports', ['Port','Total'], args.outputBaseName+'_Top'+args.c+'Ports', args.outputFormat) 293 | print bcolors.OKGREEN + '[+] - Generated \'Top '+args.c+' Most Common Ports\' output in '+args.outputFormat+' format.' + bcolors.ENDC 294 | if args.hostlist: 295 | if args.outputFormat != 'html': 296 | print bcolors.WARNING + '[!] - \'All Hosts\' Services\' output is only generated in HTML.' + bcolors.ENDC 297 | res = getHostPortList() 298 | if not res: 299 | print bcolors.WARNING + '[!] - No detailed host service info found, related output will not be generated.' + bcolors.ENDC 300 | else: 301 | generateHostPortListOutput(res, args.outputBaseName+'_hostList.html') 302 | print bcolors.OKGREEN + '[+] - Generated \'All Hosts\' Services\' output in html format.' + bcolors.ENDC 303 | if args.bports: 304 | res = getPorts(int(args.c),'bottom',args.tports) 305 | if not res: 306 | print bcolors.WARNING + '[!] - No port info found, related output will not be generated.' + bcolors.ENDC 307 | else: 308 | generateOutput(res, 'Top '+args.c+' Least Common Ports', ['Port','Total'], args.outputBaseName+'_Bottom'+args.c+'Ports', args.outputFormat) 309 | print bcolors.OKGREEN + '[+] - Generated \'Top '+args.c+' Least Common Ports\' output in '+args.outputFormat+' format.' + bcolors.ENDC 310 | if args.tservices: 311 | res = getServices(int(args.c),'top') 312 | if not res: 313 | print bcolors.WARNING + '[!] - No service info found, related output will not be generated.' + bcolors.ENDC 314 | else: 315 | generateOutput(res, 'Top '+args.c+' Most Common Services', ['Service','Total'], args.outputBaseName+'_Top'+args.c+'Services', args.outputFormat) 316 | print bcolors.OKGREEN + '[+] - Generated \'Top '+args.c+' Most Common Services\' output in '+args.outputFormat+' format.' + bcolors.ENDC 317 | if args.bservices: 318 | res = getServices(int(args.c),'bottom') 319 | if not res: 320 | print bcolors.WARNING + '[!] - No service info found, related output will not be generated.' + bcolors.ENDC 321 | else: 322 | generateOutput(res, 'Top '+args.c+' Least Common Services', ['Service','Total'], args.outputBaseName+'_Bottom'+args.c+'Services', args.outputFormat) 323 | print bcolors.OKGREEN + '[+] - Generated \'Top '+args.c+' Least Common Services\' output in '+args.outputFormat+' format.' + bcolors.ENDC 324 | if args.tos: 325 | res = getOperSys(int(args.c),'top') 326 | if not res: 327 | print bcolors.WARNING + '[!] - No operating system info found, related output will not be generated.' + bcolors.ENDC 328 | else: 329 | generateOutput(res, 'Top '+args.c+' Most Common Operating Systems', ['Operating System', 'Total'], args.outputBaseName+'_Top'+args.c+'OperatingSystems', args.outputFormat) 330 | print bcolors.OKGREEN + '[+] - Generated \'Top '+args.c+' Most Common Operating Systems\' output in '+args.outputFormat+' format.' + bcolors.ENDC 331 | if args.bos: 332 | res = getOperSys(int(args.c),'bottom') 333 | if not res: 334 | print bcolors.WARNING + '[!] - No operating system info found, related output will not be generated.' + bcolors.ENDC 335 | else: 336 | generateOutput(res, args.c+' Least Common Operating Systems', ['Operating System', 'Total'], args.outputBaseName+'_Bottom'+args.c+'OperatingSystems', args.outputFormat) 337 | print bcolors.OKGREEN + '[+] - Generated \'Top '+args.c+' Least Common Operating Systems\' output in '+args.outputFormat+' format.' + bcolors.ENDC 338 | if args.thosts: 339 | res = getHosts(int(args.c),'top') 340 | if not res: 341 | print bcolors.WARNING + '[!] - No host info found, related output will not be generated.' + bcolors.ENDC 342 | else: 343 | generateOutput(res, 'Top '+args.c+' Hosts with Most Open Ports', ['Host', 'Total'], args.outputBaseName+'_Top'+args.c+'Hosts', args.outputFormat) 344 | print bcolors.OKGREEN + '[+] - Generated \'Top '+args.c+' Hosts with Most Open Ports\' output in '+args.outputFormat+' format.' + bcolors.ENDC 345 | if args.bhosts: 346 | res = getHosts(int(args.c),'bottom') 347 | if not res: 348 | print bcolors.WARNING + '[!] - No host info found, related output will not be generated.' + bcolors.ENDC 349 | else: 350 | generateOutput(res, 'Top '+args.c+' Hosts with Least Open Ports', ['Host', 'Total'], args.outputBaseName+'_Bottom'+args.c+'Hosts', args.outputFormat) 351 | print bcolors.OKGREEN + '[+] - Generated \'Top '+args.c+' Hosts with Least Open Ports\' output in '+args.outputFormat+' format.' + bcolors.ENDC 352 | print bcolors.OKGREEN + '[+] - Done.' + bcolors.ENDC 353 | --------------------------------------------------------------------------------