Browse Source

refined nmap-to-json.py

Marius Schwarz 4 years ago
parent
commit
e1648325bb
1 changed files with 131 additions and 55 deletions
  1. 131 55
      nmap-to-json.py

+ 131 - 55
nmap-to-json.py

@@ -1,116 +1,192 @@
 #!/usr/bin/python3
 import json
 import sys
+from enum import Enum
 
+DEBUG = False
+full_output = False
+hosts = []
+
+# Describe the port fields
 class Port:
-    port = 0
+    port_number = 0
     state = ""
-    alias = ""
-    version = ""
     protocol = ""
+    owner = ""
+    service = ""
+    sunRPCinfo = ""
+    version = ""
 
     def __str__(self):
-        return f"Port {self.port} - Version: {self.version}"
-
-    def todict(self):
+        return f"Port {self.port_number} - Version: {self.version}"
+
+    def todict(self, full=False):
+        if full:
+            return {
+                    "port":self.port_number,
+                    "state":self.state,
+                    "protocol":self.protocol,
+                    "owner":self.owner,
+                    "service":self.service,
+                    "RPCInfo":self.sunRPCinfo,
+                    "version":self.version,
+                    }
         return {
-                "port":self.port,
-                "alias":self.alias,
-                "version":self.version,
+                "port":self.port_number,
                 "protocol":self.protocol,
+                "service":self.service,
+                "version":self.version,
                 }
 
 
+# Enum to represent the Host Status Field
+class Status(Enum):
+    Up = 1
+    Down = 2
+    Unknown = 3
+
+    def from_string(status):
+        if status.lower() == "up":
+            return Status.Up
+        if status.lower() == "down":
+            return Status.Down
+        if status.lower() == "unknown":
+            return Status.Unknown
+
+
+# Class to represent all the host attributes
 class Host:
     ip = ""
+    status = None
     hostname = ""
     ports = [] # Array of Port() items
 
-    def __str__(self):
-        return f"{self.ip} [{self.hostname}]"
-
-    def todict(self):
+    def todict(self, full):
         return {
                 "ip":self.ip,
                 "hostname":self.hostname,
-                "ports":[port.todict() for port in self.ports]
+                "ports":[port.todict(full) for port in self.ports],
+                "status":str(self.status.value)
             }
 
 
 def open_gnmap(filename):
     with open(filename, "r") as gnmap:
+        # split into lines and only take the ones that begin with "Host:"
         lines = gnmap.read().strip().split("\n")
         lines = [l for l in lines if not l.startswith("#") and l.startswith("Host:")]
+
     return lines
 
 
-# parse the ports part
-def parse_ports(portline):
-    portlist = portline.split("/,")
-    outports = []
+# parse every single line
+def parse_line(line):
+    global hosts
+
+    # split line into different fields
+    fields = line.split("\t")
+
+    # parse the different fields
+    out_host = parse_fields(fields)
+    merge_hosts(out_host)
+
+
+def parse_host_field(field):
+
+    host = Host()
+    subfields = field.split(" (")
+    host.ip = subfields[0]
+    host.hostname =subfields[1].replace(")", "")
+    if DEBUG: print(f"DEBUG: parse_host_field::ip={host.ip};hostname={host.hostname}")
+    return host
+
+def parse_ports_field(host, field):
+    portlist = field.split("/,")
+
+    # loop through the ports and append them to the host
     for p in portlist:
         port = Port()
         portparts = p.split("/")
-        port.port = portparts[0]
+        port.port_number = portparts[0]
         port.state = portparts[1]
         port.protocol = portparts[2]
-        port.alias = portparts[4]
+        port.owner = portparts[3]
+        port.service = portparts[4]
+        port.sunRPCinfo = portparts[5]
         port.version = portparts[6]
-        # only add 'open' ports to the list
-        #if port.state == "open":
-        outports.append(port)
-    return outports
+        host.ports.append(port)
+    return host
 
+def parse_status_field(host, field):
+    host.status = Status.from_string(field)
+    return host
 
-# list for all hosts
-all_hosts = []
-unique = []
-def parse_line(line):
 
-    host = Host()
-    line = line.strip().replace(" ", ";").replace("\t", ";").replace("  ", "")
+# Jumptable for the different fields
+field_parser = {
+        "Host": parse_host_field,
+        "Ports": parse_ports_field,
+        "Status": parse_status_field,
+}
 
-    # split line by space for parts
-    parts = line.split(";")
 
-    # remove the line with the status
-    if parts[3] == "Status:":
-        return
+# if no parser for that type of field is implemented
+def not_implemented(host, field_type):
+    return host
+
+
+def merge_hosts(host):
+    global hosts
+
+    for ih in hosts:
+        if ih.ip == host.ip:
+            ih.ports = host.ports
+            break
+    else:
+        hosts.append(host)
 
-    # dont parse hosts twice
-    ip = parts[1]
-    if ip in unique:
-        return
+# parsed the different fields, each separeted by a space
+def parse_fields(fields):
 
-    # get hostname and remove brackets
-    hostname = parts[2] if parts[2] != "()" else ""
-    hostname = hostname.replace("(", "").replace(")", "")
+    # create a Host instance
+    host = Host()
 
-    ports = "".join(parts[4:])
-    portlist = parse_ports(ports)
+    # loop each field that is present
+    for field in fields:
+        field_type, field_value = field.split(": ")
 
-    # set needed properties
-    host.ip = ip
-    host.hostname = hostname
-    host.ports = portlist
-    unique.append(ip)
+        if DEBUG: print(f"DEBUG: field_type={field_type}")
+        if DEBUG: print(f"DEBUG: field_content={field_value}")
+        # Host Field is the most important one
+        if field_type == "Host":
+            host = parse_host_field(field_value)
+        else:
+            host = field_parser.get(field_type, not_implemented)(host, field_value)
 
-    # return the crafted host
     return host
 
 
+def hosts_to_json(full_output):
+    global hosts
+    out = []
+    for host in hosts:
+        out.append(host.todict(full_output))
+    return out
+
+
 # check usage
 if len(sys.argv) == 1:
     print(f"usage: {sys.argv[0]} <scan.gnmap>")
     exit(1)
+elif len(sys.argv) == 3 and sys.argv[2] == "--full":
+    full_output = True
 
-# open gnmap file
+# Open the gnmap file
 lines = open_gnmap(sys.argv[1])
 
 # parse file
 for line in lines:
-    outhost = parse_line(line)
-    if outhost != None:
-        all_hosts.append(outhost.todict())
+    parse_line(line)
+
 # dump as json
-print(json.dumps(all_hosts))
+print(json.dumps(hosts_to_json(full_output)))