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
Copy 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:
Copy 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
Copy 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
Copy 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:
Copy 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:
Reference
Last updated 6 months ago