Network mapper

Nmap scanner will be used for all networks host and ports discovery tasks. The current scripts presented on this post will only check for IP, protocol and ports, and the traceroute results but can be changed to add more properties to the objects.

Nmap --traceroute

nmap -sS -p22,80,443,445,8080 -oX network_one.xml --traceroute 192.168.0.0/20

Mandatory options are:

  • -oX (or -oA)

    • to save the output file in XML format

  • –traceroute

    • to scan for the IP path of the packets until the destination

Setup Neo4J

The next example assumes disabled authentication in the neo4j database. To do this configure in the neo4j.conf file the following property:

dbms.security.auth_enabled=false

This is optional but if you do not disabled it you will have to change the authentication in the scripts presented.

Nmap XML to Neo4J

import sys, json
from xml.dom.minidom import parse
from neo4j import GraphDatabase

NEO4J_URL = "bolt://127.0.0.1:7687"

created = []

driver = GraphDatabase.driver(NEO4J_URL)
session = driver.session(database="neo4j")


def add_node(tx, data):
    result = tx.run("CREATE (h:Host) SET h.address = $address RETURN id(h)", address=data['data']['id'])
    return result.single()[0]


def add_nmap_results(tx, results):
    address, proto, nr, state, service = results
    tx.run("MATCH (h:Host {address: $address}) MERGE (h)-[:has_port]->(:Port {number: $nr, proto: $proto, service: $service, state: $state})", address=address, nr=nr, state=state, proto=proto, service=service)


def relate_nodes(tx, nodes):
    first, last = nodes
    tx.run("MATCH (src:Host {address: $first}), (dst:Host {address: $last}) MERGE (src)-[:connects_to]->(dst)", first=first, last=last)


src = "SRC"
data = {'data': {'id': src}}
id = session.execute_write(add_node, data)


def parse_elements(dom):
    hosts = dom.getElementsByTagName('host')
    for host in hosts:
        address = host.getElementsByTagName('address')[0].getAttribute('addr')
        print("Adding " + address)
        data = {'data': {'id': address}}
        id = session.execute_write(add_node, data)
        created.append(address)

        ports = host.getElementsByTagName('ports')[0]
        for port in ports.getElementsByTagName('port'):
            proto, nr = port.getAttribute('protocol'), port.getAttribute('portid')
            state = port.getElementsByTagName('state')[0].getAttribute('state')
            try: service = port.getElementsByTagName('service')[0].getAttribute('name')
            except: service = ""
            session.execute_write(add_nmap_results, (address, proto, nr, state, service))

        try:
            trace = host.getElementsByTagName('trace')[0]
            hops = trace.getElementsByTagName('hop')
            last = src
            for hop in hops:
                ip = hop.getAttribute('ipaddr')
                if ip not in created:
                    data = {'data': {'id': ip}}
                    session.execute_write(add_node, data)
                    created.append(ip)
                session.execute_write(relate_nodes, (last, ip))
                last = ip
        except:
            pass

for arg in sys.argv[1:]:
    dom = parse(arg)
    parse_elements(dom)

Converting to a interactive HTML

import sys, json, requests

db = {'nodes': [], 'edges': []}

response = requests.post("http://127.0.0.1:7474/db/neo4j/tx", json={
  "statements": [
    {
      #"statement": "match p=((n1:Host)-[:connects_to]->(n2:Host)-[:has_port*]->(port:Port)) return p",
      "statement": "match p=((n1:Host)-[:connects_to]->(n2:Host)) return p",
      "resultDataContents": ["graph"]
    }
  ]
})

if response.status_code == 201:
    results = response.json()
    for result in results['results']:
        for data in result['data']:
            for node in data['graph']['nodes']:
                if node['labels'][0] == "Host":
                    label = node['properties']['address']
                elif node['labels'][0] == "Port":
                    label = node['properties']['number']
                else:
                    raise Exception("Cannot handle it!")

                n = {'id': node['id'], 'classes': node['labels'], 'data': node['properties'], 'label': label}
                db['nodes'].append({'data': n})
            for node in data['graph']['relationships']:
                n = {'id': node['id'], 'source': node['startNode'], 'target': node['endNode'], 'type': node['type']}
                db['edges'].append({'data': n})

elements = {'elements': db,
            'layout': {'name': 'euler', 'randomize': True, 'animate': True},
            'style': [
                {'selector': 'node', 'style': {'label': 'data(label)'}},
                {'selector': 'edge', 'style': {'label': 'data(type)'}}
           ]
        }

print("""
<html>
<body>
<div id='cy' class='mw-100 mh-100' style="width: 1000px; height: 1000px"></div>
<a href="javascript:redraw()">Reorganize</a>
</body>
<script src="js/cytoscape.min.js"></script>
<script src="js/cytoscape-euler.js"></script>
<script src="js/popper.min.js"></script>
<script src="js/cytoscape-popper.js"></script>
<script src="js/tippy-bundle.umd.min.js"></script>
<script src="js/bootstrap.bundle.min.js"></script>
<script>
function redraw(){
  newlayout = cy.layout({name: "euler"});
  newlayout.run();
}

var cy = cytoscape({
    container: document.getElementById('cy'),
    elements: %s,
    layout: %s,
    style: %s
});
</script>
</html>
""" % (json.dumps(elements['elements']), json.dumps(elements['layout']), json.dumps(elements['style'])))

Dependencies:

The final result will look something like the following image. The HTML is pannable and zoomable and you can select nodes.

Bonus

If you can identify nodes from cy in the javascript console, you can use:

cy.elements().forEach( (elem) => {
    if(elem.isNode()){
        var id = elem.id();

        if(elem.data('data.address') == "SRC"){
          elem.style({'background-color': 'green'});
        }else if(elem.data('data.address') == "192.168.2.71") {
          elem.style({'background-color': 'blue'});
        }
        else if(elem.data('data.address') == "192.168.2.251") {
          elem.style({'background-color': 'red'});
        }
    }
  });

Use the following functions to redraw the graph:

redraw();

Reference

Last updated