[
  {
    "path": "README.md",
    "content": "# Nmap-XML-Parser\nConverts Nmap XML output to csv file, and other useful functions. Ignores hosts that are down and ports that are not open.\n\n## Usage\n\n### Convert Nmap output to csv file\n`python3 nmap_xml_parser.py -f nmap_scan.xml -csv nmap_scan.csv`\n\n### Display scan information to the terminal\n`python3 nmap_xml_parser.py -f nmap_scan.xml -p`\n\n### Display only IP addresses\n`python3 nmap_xml_parser.py -f nmap_scan.xml -ip`\n\n### Display IP addresses/ports in URL friendly format\n> Displays in format http(s)://ipaddr:port if port is a possible web port\n\n`python3 nmap_xml_parser.py -f nmap_scan.xml -pw`\n\n### Display least common open ports\n> Displays the 10 least common open ports\n\n`python3 nmap_xml_parser.py -f nmap_scan.xml -lc 10`\n\n### Display most common open ports\n> Displays the 10 most common open ports\n\n`python3 nmap_xml_parser.py -f nmap_scan.xml -mc 10`\n\n### Display only IP addresses with a specified open port\n> Displays only IP addresses where port 23 is open\n\n`python3 nmap_xml_parser.py -f nmap_scan.xml -fp 23`\n"
  },
  {
    "path": "nmap_xml_parser.py",
    "content": "#!/usr/bin/env python\r\n\r\n__author__ = 'Jake Miller (@LaconicWolf)'\r\n__date__ = '20171220'\r\n__version__ = '0.01'\r\n__description__ = \"\"\"Parses the XML output from an nmap scan. The user\r\n                  can specify whether the data should be printed,\r\n                  displayed as a list of IP addresses, or output to\r\n                  a csv file. Will append to a csv if the filename\r\n                  already exists.\r\n                  \"\"\"\r\n\r\nimport xml.etree.ElementTree as etree\r\nimport os\r\nimport csv\r\nimport argparse\r\nfrom collections import Counter\r\nfrom time import sleep\r\n\r\ndef get_host_data(root):\r\n    \"\"\"Traverses the xml tree and build lists of scan information\r\n    and returns a list of lists.\r\n    \"\"\"\r\n    host_data = []\r\n    hosts = root.findall('host')\r\n    for host in hosts:\r\n        addr_info = []\r\n\r\n        # Ignore hosts that are not 'up'\r\n        if not host.findall('status')[0].attrib['state'] == 'up':\r\n            continue\r\n        \r\n        # Get IP address and host info. If no hostname, then ''\r\n        ip_address = host.findall('address')[0].attrib['addr']\r\n        host_name_element = host.findall('hostnames')\r\n        try:\r\n            host_name = host_name_element[0].findall('hostname')[0].attrib['name']\r\n        except IndexError:\r\n            host_name = ''\r\n        \r\n        # If we only want the IP addresses from the scan, stop here\r\n        if args.ip_addresses:\r\n            addr_info.extend((ip_address, host_name))\r\n            host_data.append(addr_info)\r\n            continue\r\n        \r\n        # Get the OS information if available, else ''\r\n        try:\r\n            os_element = host.findall('os')\r\n            os_name = os_element[0].findall('osmatch')[0].attrib['name']\r\n        except IndexError:\r\n            os_name = ''\r\n        \r\n        # Get information on ports and services\r\n        try:\r\n            port_element = host.findall('ports')\r\n            ports = port_element[0].findall('port')\r\n            for port in ports:\r\n                port_data = []\r\n\r\n                if args.udp_open:\r\n                    # Display both open ports and open}filtered ports\r\n                    if not 'open' in port.findall('state')[0].attrib['state']:\r\n                        continue\r\n                else:\r\n                    # Ignore ports that are not 'open'\r\n                    if not port.findall('state')[0].attrib['state'] == 'open':\r\n                        continue\r\n                \r\n                proto = port.attrib['protocol']\r\n                port_id = port.attrib['portid']\r\n                service = port.findall('service')[0].attrib['name']\r\n                try:\r\n                    product = port.findall('service')[0].attrib['product']\r\n                except (IndexError, KeyError):\r\n                    product = ''      \r\n                try:\r\n                    servicefp = port.findall('service')[0].attrib['servicefp']\r\n                except (IndexError, KeyError):\r\n                    servicefp = ''\r\n                try:\r\n                    script_id = port.findall('script')[0].attrib['id']\r\n                except (IndexError, KeyError):\r\n                    script_id = ''\r\n                try:\r\n                    script_output = port.findall('script')[0].attrib['output']\r\n                except (IndexError, KeyError):\r\n                    script_output = ''\r\n\r\n                # Create a list of the port data\r\n                port_data.extend((ip_address, host_name, os_name,\r\n                                  proto, port_id, service, product, \r\n                                  servicefp, script_id, script_output))\r\n                \r\n                # Add the port data to the host data\r\n                host_data.append(port_data)\r\n\r\n        # If no port information, just create a list of host information\r\n        except IndexError:\r\n            addr_info.extend((ip_address, host_name))\r\n            host_data.append(addr_info)\r\n    return host_data\r\n\r\ndef parse_xml(filename):\r\n    \"\"\"Given an XML filename, reads and parses the XML file and passes the \r\n    the root node of type xml.etree.ElementTree.Element to the get_host_data\r\n    function, which will futher parse the data and return a list of lists\r\n    containing the scan data for a host or hosts.\"\"\"\r\n    try:\r\n        tree = etree.parse(filename)\r\n    except Exception as error:\r\n        print(\"[-] A an error occurred. The XML may not be well formed. \"\r\n              \"Please review the error and try again: {}\".format(error))\r\n        exit()\r\n    root = tree.getroot()\r\n    scan_data = get_host_data(root)\r\n    return scan_data\r\n\r\ndef parse_to_csv(data):\r\n    \"\"\"Given a list of data, adds the items to (or creates) a CSV file.\"\"\"\r\n    if not os.path.isfile(csv_name):\r\n        csv_file = open(csv_name, 'w', newline='')\r\n        csv_writer = csv.writer(csv_file)\r\n        top_row = [\r\n            'IP', 'Host', 'OS', 'Proto', 'Port',\r\n            'Service', 'Product', 'Service FP',\r\n            'NSE Script ID', 'NSE Script Output', 'Notes'\r\n        ]\r\n        csv_writer.writerow(top_row)\r\n        print('\\n[+] The file {} does not exist. New file created!\\n'.format(\r\n                csv_name))\r\n    else:\r\n        try:\r\n            csv_file = open(csv_name, 'a', newline='')\r\n        except PermissionError as e:\r\n            print(\"\\n[-] Permission denied to open the file {}. \"\r\n                  \"Check if the file is open and try again.\\n\".format(csv_name))\r\n            print(\"Print data to the terminal:\\n\")\r\n            if args.debug:\r\n                print(e)\r\n            for item in data:\r\n                print(' '.join(item))\r\n            exit()\r\n        csv_writer = csv.writer(csv_file)\r\n        print('\\n[+] {} exists. Appending to file!\\n'.format(csv_name))\r\n    for item in data:\r\n        csv_writer.writerow(item)\r\n    csv_file.close()        \r\n\r\ndef list_ip_addresses(data):\r\n    \"\"\"Parses the input data to return only the IP address information\"\"\"\r\n    ip_list = [item[0] for item in data]\r\n    sorted_set = sorted(set(ip_list))\r\n    addr_list = [ip for ip in sorted_set]\r\n    return addr_list\r\n\r\ndef print_web_ports(data):\r\n    \"\"\"Examines the port information and prints out the IP and port \r\n    info in URL format (https://ipaddr:port/).\r\n    \"\"\"\r\n\r\n    # http and https port numbers came from experience as well as\r\n    # searching for http on th following website:\r\n    # https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers\r\n    http_port_list = ['80', '280', '81', '591', '593', '2080', '2480', '3080', \r\n                      '4080', '4567', '5080', '5104', '5800', '6080',\r\n                      '7001', '7080', '7777', '8000', '8008', '8042', '8080',\r\n                      '8081', '8082', '8088', '8180', '8222', '8280', '8281',\r\n                      '8530', '8887', '9000', '9080', '9090', '16080']                    \r\n    https_port_list = ['832', '981', '1311', '7002', '7021', '7023', '7025',\r\n                       '7777', '8333', '8531', '8888']\r\n    for item in data:\r\n        ip = item[0]\r\n        port = item[4]\r\n        if port.endswith('43') and port != \"143\" or port in https_port_list:\r\n            print(\"https://{}:{}\".format(ip, port))\r\n        elif port in http_port_list:\r\n            print(\"http://{}:{}\".format(ip, port))\r\n        else:\r\n            continue    \r\n    \r\ndef least_common_ports(data, n):\r\n    \"\"\"Examines the port index from data and prints the least common ports.\"\"\"\r\n    c = Counter()\r\n    for item in data:\r\n        try:\r\n            port = item[4]\r\n            c.update([port])\r\n        except IndexError as e:\r\n            if args.debug:\r\n                print(e)\r\n            continue\r\n    print(\"{0:8} {1:15}\\n\".format('PORT', 'OCCURENCES'))\r\n    for p in c.most_common()[:-n-1:-1]:\r\n        print(\"{0:5} {1:8}\".format(p[0], p[1]))\r\n\r\ndef most_common_ports(data, n):\r\n    \"\"\"Examines the port index from data and prints the most common ports.\"\"\"\r\n    c = Counter()\r\n    for item in data:\r\n        try:\r\n            port = item[4]\r\n            c.update([port])\r\n        except IndexError as e:\r\n            if args.debug:\r\n                print(e)\r\n            continue\r\n    print(\"{0:8} {1:15}\\n\".format('PORT', 'OCCURENCES'))\r\n    for p in c.most_common(n):\r\n        print(\"{0:5} {1:8}\".format(p[0], p[1]))\r\n\r\ndef print_filtered_port(data, filtered_port):\r\n    \"\"\"Examines the port index from data and see if it matches the \r\n    filtered_port. If it matches, print the IP address.\r\n    \"\"\"\r\n    for item in data:\r\n        try:\r\n            port = item[4]\r\n        except IndexError as e:\r\n            if args.debug:\r\n                print(e)\r\n            continue\r\n        if port == filtered_port:\r\n            print(item[0])\r\n\r\ndef print_data(data):\r\n    \"\"\"Prints the data to the terminal.\"\"\"\r\n    for item in data:\r\n        print(' '.join(item))\r\n\r\ndef main():\r\n    \"\"\"Main function of the script.\"\"\"\r\n    for filename in args.filename:\r\n\r\n        # Checks the file path\r\n        if not os.path.exists(filename):\r\n            parser.print_help()\r\n            print(\"\\n[-] The file {} cannot be found or you do not have \"\r\n                  \"permission to open the file.\".format(filename))\r\n            continue\r\n\r\n        if not args.skip_entity_check:\r\n            # Read the file and check for entities\r\n            with open(filename) as fh:\r\n                contents = fh.read()\r\n                if '<!entity' in contents.lower():\r\n                    print(\"[-] Error! This program does not permit XML \"\r\n                          \"entities. Ignoring {}\".format(filename))\r\n                    print(\"[*] Use -s (--skip_entity_check) to ignore this \"\r\n                          \"check for XML entities.\")\r\n                    continue\r\n        data = parse_xml(filename)\r\n        if not data:\r\n            print(\"[*] Zero hosts identitified as 'Up' or with 'open' ports. \"\r\n                  \"Use the -u option to display ports that are 'open|filtered'. \"\r\n                  \"Exiting.\")\r\n            exit()\r\n        if args.csv:\r\n            parse_to_csv(data)\r\n        if args.ip_addresses:\r\n            addrs = list_ip_addresses(data)\r\n            for addr in addrs:\r\n                print(addr)\r\n        if args.print_all:\r\n            print_data(data)\r\n        if args.filter_by_port:\r\n            print_filtered_port(data, args.filter_by_port)\r\n        if args.print_web_ports:\r\n            print_web_ports(data)\r\n        if args.least_common_ports:\r\n            print(\"\\n{} LEAST COMMON PORTS\".format(filename.upper()))\r\n            least_common_ports(data, args.least_common_ports)\r\n        if args.most_common_ports:\r\n            print(\"\\n{} MOST COMMON PORTS\".format(filename.upper()))\r\n            most_common_ports(data, args.most_common_ports)\r\n\r\nif __name__ == '__main__':\r\n    parser = argparse.ArgumentParser()\r\n    parser.add_argument(\"-d\", \"--debug\",\r\n                        help=\"Display error information\",\r\n                        action=\"store_true\")\r\n    parser.add_argument(\"-s\", \"--skip_entity_check\",\r\n                        help=\"Skip the check for XML entities\",\r\n                        action=\"store_true\")\r\n    parser.add_argument(\"-p\", \"--print_all\",\r\n                        help=\"Display scan information to the screen\", \r\n                        action=\"store_true\")\r\n    parser.add_argument(\"-pw\", \"--print_web_ports\",\r\n                        help=\"Display IP addresses/ports in URL format \"\r\n                             \"(http://ipaddr:port)\",\r\n                        action=\"store_true\")\r\n    parser.add_argument(\"-ip\", \"--ip_addresses\",\r\n                        help=\"Display a list of ip addresses\",\r\n                        action=\"store_true\")\r\n    parser.add_argument(\"-csv\", \"--csv\",\r\n                        nargs='?', const='scan.csv',\r\n                        help=\"Specify the name of a csv file to write to. \"\r\n                             \"If the file already exists it will be appended\")\r\n    parser.add_argument(\"-f\", \"--filename\",\r\n                        nargs='*',\r\n                        help=\"Specify a file containing the output of an nmap \"\r\n                             \"scan in xml format.\")\r\n    parser.add_argument(\"-lc\",\"--least_common_ports\",\r\n                        type=int, \r\n                        help=\"Displays the least common open ports.\")\r\n    parser.add_argument(\"-mc\", \"--most_common_ports\",\r\n                        type=int, \r\n                        help=\"Displays the most common open ports.\")\r\n    parser.add_argument(\"-fp\", \"--filter_by_port\", \r\n                        help=\"Displays the IP addresses that are listenting on \"\r\n                             \"a specified port\")\r\n    parser.add_argument(\"-u\", \"--udp_open\", \r\n                        help=\"Displays the UDP ports identified as \"\r\n                             \"open|filtered\",\r\n                        action=\"store_true\")\r\n    args = parser.parse_args()\r\n\r\n    if not args.filename:\r\n        parser.print_help()\r\n        print(\"\\n[-] Please specify an input file to parse. \"\r\n              \"Use -f <nmap_scan.xml> to specify the file\\n\")\r\n        exit()\r\n    if not args.ip_addresses and not args.csv and not args.print_all \\\r\n                and not args.print_web_ports and not args.least_common_ports \\\r\n                and not args.most_common_ports and not args.filter_by_port:\r\n        parser.print_help()\r\n        print(\"\\n[-] Please choose an output option. Use -csv, -ip, or -p\\n\")\r\n        exit()\r\n    csv_name = args.csv\r\n    main()"
  }
]