#!/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 import argparse from collections import Counter from sys import version from time import sleep if not version.startswith('3'): print('\nThis script is intended to be run with Python3. If using another version and encounter an error, try using Python3\n') sleep(3) def get_xml_root(xml): """Parses an xml file and returns the tree.""" try: tree = etree.parse(xml) 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() return tree.getroot() def get_host_data(root): """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 = '' 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.""" root = get_xml_root(filename) return get_host_data(root) def parse_to_csv(data): """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)) 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("Print data to the terminal:\n") if args.debug: print(e) for item in data: print(' '.join(item)) 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 = [item[0] for item in data] sorted_set = sorted(set(ip_list)) 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/). """ # 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 http_port_list = ['80', '280', '81', '591', '593', '2080', '2480', '3080', '4080', '4567', '5080', '5104', '5800', '6080', '7001', '7080', '7777', '8000', '8008', '8042', '8080', '8081', '8082', '8088', '8180', '8222', '8280', '8281', '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] if port.endswith('43') and port != "143" or port in https_port_list: print("https://{}:{}".format(ip, port)) elif port in http_port_list: print("http://{}:{}".format(ip, port)) else: continue def least_common_ports(data, n): """Examines the port index from data and prints the least common ports.""" c = Counter() for item in data: try: port = item[4] c.update([port]) except IndexError as e: 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.""" c = Counter() for item in data: try: port = item[4] c.update([port]) except IndexError as e: 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. """ for item in data: try: port = item[4] except IndexError as e: if args.debug: print(e) continue if port == filtered_port: print(item[0]) def print_data(data): """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") 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") exit() csv_name = args.csv main()