├── .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')
114 | for i in range(len(headers)):
115 | f.write('| \n'+headers[i]+'\n | \n')
116 | f.write('
\n')
117 | for item in itemList:
118 | f.write('\n')
119 | for i in range(len(headers)):
120 | f.write('| '+str(item[i])+' | ')
121 | f.write('
\n')
122 | f.write('
\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 |
--------------------------------------------------------------------------------