From 403a7938ef1a822e7adefefb8088684ce513bf13 Mon Sep 17 00:00:00 2001 From: Jake Miller Date: Mon, 23 Jul 2018 20:01:26 -0500 Subject: [PATCH] Update nmap_xml_parser.py --- nmap_xml_parser.py | 170 +++++++++++++++++++++++++-------------------- 1 file changed, 93 insertions(+), 77 deletions(-) diff --git a/nmap_xml_parser.py b/nmap_xml_parser.py index e99832e..a21ec6a 100644 --- a/nmap_xml_parser.py +++ b/nmap_xml_parser.py @@ -1,3 +1,17 @@ +#!/usr/bin/env python3 + + +__author__ = 'Jake Miller (@LaconicWolf)' +__date__ = '20171220' +__version__ = '0.01' +__description__ = """Parses the XML output from an nmap scan. The user + can specify whether the data should be printed, + displayed as a list of IP addresses, or output to + a csv file. Will append to a csv if the filename + already exists + """ + + import xml.etree.ElementTree as etree import os import csv @@ -12,125 +26,113 @@ if not version.startswith('3'): sleep(3) -__author__ = 'Jake Miller (@LaconicWolf)' -__date__ = '20171220' -__version__ = '0.01' -__description__ = '''Parses the xml output from an nmap scan. The user - can specify whether the data should be printed, - displayed as a list of IP addresses, or output to - a csv file. Will append to a csv if the filename - already exists''' - - def get_xml_root(xml): - """ Parses an xml file and returns the tree - """ + """Parses an xml file and returns the tree.""" try: tree = etree.parse(xml) - except: - print("A an error occurred. The XML may not be well formed.") + except Exception as error: + print("A an error occurred. The XML may not be well formed. Please review the error and try again: {}".format(error)) exit() - root = tree.getroot() - - return root + return tree.getroot() def get_host_data(root): - """ Goes through the xml tree and build lists of scan information + """Traverses the xml tree and build lists of scan information and returns a list of lists. """ host_data = [] hosts = root.findall('host') for host in hosts: addr_info = [] + + # Ignore hosts that are not 'up' if not host.findall('status')[0].attrib['state'] == 'up': continue - + + # Get IP address and host info. If no hostname, then '' ip_address = host.findall('address')[0].attrib['addr'] host_name_element = host.findall('hostnames') - try: host_name = host_name_element[0].findall('hostname')[0].attrib['name'] except IndexError: host_name = '' - + + # If we only want the IP addresses from the scan, stop here if args.ip_addresses: addr_info.extend((ip_address, host_name)) host_data.append(addr_info) continue - + + # Get the OS information if available, else '' try: os_element = host.findall('os') os_name = os_element[0].findall('osmatch')[0].attrib['name'] except IndexError: os_name = '' + # Get information on ports and services try: port_element = host.findall('ports') ports = port_element[0].findall('port') - for port in ports: port_data = [] + + # Ignore ports that are not 'open' if not port.findall('state')[0].attrib['state'] == 'open': continue - + proto = port.attrib['protocol'] port_id = port.attrib['portid'] service = port.findall('service')[0].attrib['name'] - try: product = port.findall('service')[0].attrib['product'] except (IndexError, KeyError): - product = '' - + product = '' try: servicefp = port.findall('service')[0].attrib['servicefp'] except (IndexError, KeyError): servicefp = '' - try: script_id = port.findall('script')[0].attrib['id'] except (IndexError, KeyError): script_id = '' - try: script_output = port.findall('script')[0].attrib['output'] except (IndexError, KeyError): script_output = '' + # Create a list of the port data port_data.extend((ip_address, host_name, os_name, proto, port_id, service, product, servicefp, script_id, script_output)) + + # Add the port data to the host data host_data.append(port_data) + # If no port information, just create a list of host information except IndexError: addr_info.extend((ip_address, host_name)) host_data.append(addr_info) - return host_data def parse_xml(filename): - """ Calls functions to read the xml and extract elements and values - """ + """Calls functions to read the xml and extract elements and values.""" root = get_xml_root(filename) - hosts = get_host_data(root) - - return hosts + return get_host_data(root) def parse_to_csv(data): - """Accepts a list and adds the items to (or creates) a CSV file. - """ + """Accepts a list and adds the items to (or creates) a CSV file.""" if not os.path.isfile(csv_name): csv_file = open(csv_name, 'w', newline='') csv_writer = csv.writer(csv_file) top_row = ['IP', 'Host', 'OS', 'Proto', 'Port', 'Service', 'Product', 'Service FP', 'NSE Script ID', 'NSE Script Output', 'Notes'] csv_writer.writerow(top_row) - print('\n [+] The file {} does not exist. New file created!\n'.format(csv_name)) + print('\n[+] The file {} does not exist. New file created!\n'.format(csv_name)) else: try: csv_file = open(csv_name, 'a', newline='') except PermissionError as e: - print("\n [-] Permission denied to open the file {}. Check if the file is open and try again.\n".format(csv_name)) + print("\n[-] Permission denied to open the file {}. Check if the file is open and try again.\n".format(csv_name)) print("Print data to the terminal:\n") if args.debug: print(e) @@ -139,32 +141,24 @@ def parse_to_csv(data): exit() csv_writer = csv.writer(csv_file) print('\n [+] {} exists. Appending to file!\n'.format(csv_name)) - for item in data: csv_writer.writerow(item) - csv_file.close() def list_ip_addresses(data): - """ Parses the input data to display only the IP address information - """ - ip_list = [] - for item in data: - ip_list.append(item[0]) - + """Parses the input data to display only the IP address information""" + ip_list = [item[0] for item in data] sorted_set = sorted(set(ip_list)) - addr_list = [] - for ip in sorted_set: - addr_list.append(ip) - + addr_list = [ip for up in sorted_set] return addr_list def print_web_ports(data): - """ Examines the port information and prints out the IP and port - info in URL format (https://ipaddr:port/) + """Examines the port information and prints out the IP and port + info in URL format (https://ipaddr:port/). """ + # http and https port numbers came from experience as well as # searching for http on th following website: # https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers @@ -175,7 +169,6 @@ def print_web_ports(data): '8530', '8887', '9000', '9080', '9090', '16080'] https_port_list = ['832', '981', '1311', '7002', '7021', '7023', '7025', '7777', '8333', '8531', '8888'] - for item in data: ip = item[0] port = item[4] @@ -188,8 +181,7 @@ def print_web_ports(data): def least_common_ports(data, n): - """ Examines the port index from data and prints the least common ports - """ + """Examines the port index from data and prints the least common ports.""" c = Counter() for item in data: try: @@ -199,15 +191,13 @@ def least_common_ports(data, n): if args.debug: print(e) continue - print("{0:8} {1:15}\n".format('PORT', 'OCCURENCES')) for p in c.most_common()[:-n-1:-1]: print("{0:5} {1:8}".format(p[0], p[1])) def most_common_ports(data, n): - """ Examines the port index from data and prints the most common ports - """ + """Examines the port index from data and prints the most common ports.""" c = Counter() for item in data: try: @@ -217,15 +207,14 @@ def most_common_ports(data, n): if args.debug: print(e) continue - print("{0:8} {1:15}\n".format('PORT', 'OCCURENCES')) for p in c.most_common(n): print("{0:5} {1:8}".format(p[0], p[1])) def print_filtered_port(data, filtered_port): - """ Examines the port index from data and see if it matches the - filtered_port. If it matches, print the IP address + """Examines the port index from data and see if it matches the + filtered_port. If it matches, print the IP address. """ for item in data: try: @@ -239,14 +228,26 @@ def print_filtered_port(data, filtered_port): def print_data(data): - """ Prints the data to the terminal - """ + """Prints the data to the terminal.""" for item in data: print(' '.join(item)) def main(): for filename in args.filename: + + # Checks the file path + if not os.path.exists(filename): + parser.print_help() + print("\n[-] The file {} cannot be found or you do not have permission to open the file.".format(filename)) + continue + + # Read the file and check for entities + with open(filename) as fh: + contents = fh.read() + if ' to specify the file\n") + print("\n[-] Please specify an input file to parse. Use -f to specify the file\n") exit() - if not args.ip_addresses and not args.csv and not args.print_all and not args.print_web_ports \ and not args.least_common_ports and not args.most_common_ports and not args.filter_by_port: parser.print_help() - print("\n [-] Please choose an output option. Use -csv, -ip, or -p\n") + print("\n[-] Please choose an output option. Use -csv, -ip, or -p\n") exit() - csv_name = args.csv main()