├── README.md └── xml2csv.py /README.md: -------------------------------------------------------------------------------- 1 | # Nmap - xml2csv 2 | Converts Nmap XML output to csv file, and other useful functions. Ignores hosts that are down and ports that are not open. 3 | 4 | As featured in, Advanced Nmap - Scanning Large Scale Networks: 5 | 6 | https://www.youtube.com/watch?v=okCNbKSdmDA 7 | 8 | ## Usage 9 | 10 | ### Convert Nmap output to csv file 11 | `python3 xml2csv.py -f nmap_scan.xml -csv nmap_scan.csv` 12 | 13 | ### Display scan information to the terminal 14 | `python3 xml2csv.py -f nmap_scan.xml -p` 15 | 16 | ### Display only IP addresses 17 | `python3 xml2csv.py -f nmap_scan.xml -ip` 18 | 19 | ### Display IP addresses/ports in URL friendly format 20 | > Displays in format http(s)://ipaddr:port if port is a possible web port 21 | 22 | `python3 xml2csv.py -f nmap_scan.xml -pw` 23 | 24 | ### Display least common open ports 25 | > Displays the 10 least common open ports 26 | 27 | `python3 xml2csv.py -f nmap_scan.xml -lc 10` 28 | 29 | ### Display most common open ports 30 | > Displays the 10 most common open ports 31 | 32 | `python3 xml2csv.py -f nmap_scan.xml -mc 10` 33 | 34 | ### Display only IP addresses with a specified open port 35 | > Displays only IP addresses where port 23 is open 36 | 37 | `python3 xml2csv.py -f nmap_scan.xml -fp 23` 38 | -------------------------------------------------------------------------------- /xml2csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | # Credit where credit is due... 5 | __author__ = 'Jake Miller (@LaconicWolf)' 6 | __date__ = '20171220' 7 | __version__ = '0.01' 8 | __description__ = """Parses the XML output from an nmap scan. The user 9 | can specify whether the data should be printed, 10 | displayed as a list of IP addresses, or output to 11 | a csv file. Will append to a csv if the filename 12 | already exists. 13 | """ 14 | 15 | import xml.etree.ElementTree as etree 16 | import os 17 | import csv 18 | import argparse 19 | from collections import Counter 20 | from time import sleep 21 | 22 | def get_host_data(root): 23 | """Traverses the xml tree and build lists of scan information 24 | and returns a list of lists. 25 | """ 26 | host_data = [] 27 | hosts = root.findall('host') 28 | for host in hosts: 29 | addr_info = [] 30 | 31 | # Ignore hosts that are not 'up' 32 | if not host.findall('status')[0].attrib['state'] == 'up': 33 | continue 34 | 35 | # Get IP address and host info. If no hostname, then '' 36 | ip_address = host.findall('address')[0].attrib['addr'] 37 | host_name_element = host.findall('hostnames') 38 | try: 39 | host_name = host_name_element[0].findall('hostname')[0].attrib['name'] 40 | except IndexError: 41 | host_name = '' 42 | 43 | # If we only want the IP addresses from the scan, stop here 44 | if args.ip_addresses: 45 | addr_info.extend((ip_address, host_name)) 46 | host_data.append(addr_info) 47 | continue 48 | 49 | # Get the OS information if available, else '' 50 | try: 51 | os_element = host.findall('os') 52 | os_name = os_element[0].findall('osmatch')[0].attrib['name'] 53 | except IndexError: 54 | os_name = '' 55 | 56 | # Get information on ports and services 57 | try: 58 | port_element = host.findall('ports') 59 | ports = port_element[0].findall('port') 60 | for port in ports: 61 | port_data = [] 62 | 63 | if args.udp_open: 64 | # Display both open ports and open}filtered ports 65 | if not 'open' in port.findall('state')[0].attrib['state']: 66 | continue 67 | else: 68 | # Ignore ports that are not 'open' 69 | if not port.findall('state')[0].attrib['state'] == 'open': 70 | continue 71 | 72 | proto = port.attrib['protocol'] 73 | port_id = port.attrib['portid'] 74 | service = port.findall('service')[0].attrib['name'] 75 | try: 76 | product = port.findall('service')[0].attrib['product'] 77 | except (IndexError, KeyError): 78 | product = '' 79 | try: 80 | servicefp = port.findall('service')[0].attrib['servicefp'] 81 | except (IndexError, KeyError): 82 | servicefp = '' 83 | try: 84 | script_id = port.findall('script')[0].attrib['id'] 85 | except (IndexError, KeyError): 86 | script_id = '' 87 | try: 88 | script_output = port.findall('script')[0].attrib['output'] 89 | except (IndexError, KeyError): 90 | script_output = '' 91 | 92 | # Create a list of the port data 93 | port_data.extend((ip_address, host_name, os_name, 94 | proto, port_id, service, product, 95 | servicefp, script_id, script_output)) 96 | 97 | # Add the port data to the host data 98 | host_data.append(port_data) 99 | 100 | # If no port information, just create a list of host information 101 | except IndexError: 102 | addr_info.extend((ip_address, host_name)) 103 | host_data.append(addr_info) 104 | return host_data 105 | 106 | def parse_xml(filename): 107 | """Given an XML filename, reads and parses the XML file and passes the 108 | the root node of type xml.etree.ElementTree.Element to the get_host_data 109 | function, which will futher parse the data and return a list of lists 110 | containing the scan data for a host or hosts.""" 111 | try: 112 | tree = etree.parse(filename) 113 | except Exception as error: 114 | print("[-] A an error occurred. The XML may not be well formed. " 115 | "Please review the error and try again: {}".format(error)) 116 | exit() 117 | root = tree.getroot() 118 | scan_data = get_host_data(root) 119 | return scan_data 120 | 121 | def parse_to_csv(data): 122 | """Given a list of data, adds the items to (or creates) a CSV file.""" 123 | if not os.path.isfile(csv_name): 124 | csv_file = open(csv_name, 'w', newline='') 125 | csv_writer = csv.writer(csv_file) 126 | top_row = [ 127 | 'IP', 'Host', 'OS', 'Proto', 'Port', 128 | 'Service', 'Product', 'Service FP', 129 | 'NSE Script ID', 'NSE Script Output', 'Notes' 130 | ] 131 | csv_writer.writerow(top_row) 132 | print('\n[+] The file {} does not exist. New file created!\n'.format( 133 | csv_name)) 134 | else: 135 | try: 136 | csv_file = open(csv_name, 'a', newline='') 137 | except PermissionError as e: 138 | print("\n[-] Permission denied to open the file {}. " 139 | "Check if the file is open and try again.\n".format(csv_name)) 140 | print("Print data to the terminal:\n") 141 | if args.debug: 142 | print(e) 143 | for item in data: 144 | print(' '.join(item)) 145 | exit() 146 | csv_writer = csv.writer(csv_file) 147 | print('\n[+] {} exists. Appending to file!\n'.format(csv_name)) 148 | for item in data: 149 | csv_writer.writerow(item) 150 | csv_file.close() 151 | 152 | def list_ip_addresses(data): 153 | """Parses the input data to return only the IP address information""" 154 | ip_list = [item[0] for item in data] 155 | sorted_set = sorted(set(ip_list)) 156 | addr_list = [ip for ip in sorted_set] 157 | return addr_list 158 | 159 | def print_web_ports(data): 160 | """Examines the port information and prints out the IP and port 161 | info in URL format (https://ipaddr:port/). 162 | """ 163 | 164 | # http and https port numbers came from experience as well as 165 | # searching for http on th following website: 166 | # https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers 167 | http_port_list = ['80', '280', '81', '591', '593', '2080', '2480', '3080', 168 | '4080', '4567', '5080', '5104', '5800', '6080', 169 | '7001', '7080', '7777', '8000', '8008', '8042', '8080', 170 | '8081', '8082', '8088', '8180', '8222', '8280', '8281', 171 | '8530', '8887', '9000', '9080', '9090', '16080'] 172 | https_port_list = ['832', '981', '1311', '7002', '7021', '7023', '7025', 173 | '7777', '8333', '8531', '8888'] 174 | for item in data: 175 | ip = item[0] 176 | port = item[4] 177 | if port.endswith('43') and port != "143" or port in https_port_list: 178 | print("https://{}:{}".format(ip, port)) 179 | elif port in http_port_list: 180 | print("http://{}:{}".format(ip, port)) 181 | else: 182 | continue 183 | 184 | def least_common_ports(data, n): 185 | """Examines the port index from data and prints the least common ports.""" 186 | c = Counter() 187 | for item in data: 188 | try: 189 | port = item[4] 190 | c.update([port]) 191 | except IndexError as e: 192 | if args.debug: 193 | print(e) 194 | continue 195 | print("{0:8} {1:15}\n".format('PORT', 'OCCURENCES')) 196 | for p in c.most_common()[:-n-1:-1]: 197 | print("{0:5} {1:8}".format(p[0], p[1])) 198 | 199 | def most_common_ports(data, n): 200 | """Examines the port index from data and prints the most common ports.""" 201 | c = Counter() 202 | for item in data: 203 | try: 204 | port = item[4] 205 | c.update([port]) 206 | except IndexError as e: 207 | if args.debug: 208 | print(e) 209 | continue 210 | print("{0:8} {1:15}\n".format('PORT', 'OCCURENCES')) 211 | for p in c.most_common(n): 212 | print("{0:5} {1:8}".format(p[0], p[1])) 213 | 214 | def print_filtered_port(data, filtered_port): 215 | """Examines the port index from data and see if it matches the 216 | filtered_port. If it matches, print the IP address. 217 | """ 218 | for item in data: 219 | try: 220 | port = item[4] 221 | except IndexError as e: 222 | if args.debug: 223 | print(e) 224 | continue 225 | if port == filtered_port: 226 | print(item[0]) 227 | 228 | def print_data(data): 229 | """Prints the data to the terminal.""" 230 | for item in data: 231 | print(' '.join(item)) 232 | 233 | def main(): 234 | """Main function of the script.""" 235 | for filename in args.filename: 236 | 237 | # Checks the file path 238 | if not os.path.exists(filename): 239 | parser.print_help() 240 | print("\n[-] The file {} cannot be found or you do not have " 241 | "permission to open the file.".format(filename)) 242 | continue 243 | 244 | if not args.skip_entity_check: 245 | # Read the file and check for entities 246 | with open(filename) as fh: 247 | contents = fh.read() 248 | if ' to specify the file\n") 324 | exit() 325 | if not args.ip_addresses and not args.csv and not args.print_all \ 326 | and not args.print_web_ports and not args.least_common_ports \ 327 | and not args.most_common_ports and not args.filter_by_port: 328 | parser.print_help() 329 | print("\n[-] Please choose an output option. Use -csv, -ip, or -p\n") 330 | exit() 331 | csv_name = args.csv 332 | main() 333 | --------------------------------------------------------------------------------