Skip to content

Commit 4f5c3fb

Browse files
committed
Merge pull request savon-noir#45 from savon-noir/es
Merge of Elasticsearch plugin and example + added cpe_list
2 parents e64dd8a + 615e9c7 commit 4f5c3fb

6 files changed

Lines changed: 199 additions & 0 deletions

File tree

README.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ Examples
8585

8686
Some codes samples are available in the examples directory or in the `documentation`_.
8787

88+
Among other example, you notice an sample code pushing nmap scan reports in an ElasticSearch instance and allowing you to create fancy dashboards in Kibana like the screenshot below:
89+
90+
.. image:: https://github.com/savon-noir/python-libnmap/blob/es/examples/kibanalibnmap.png
91+
:alt: Kibanane
92+
:align: center
93+
8894
Contributors
8995
------------
9096

examples/elastikibana.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
from libnmap.parser import NmapParser
5+
from elasticsearch import Elasticsearch
6+
from datetime import datetime
7+
import pygeoip
8+
9+
10+
def store_report(nmap_report, database, index):
11+
rval = True
12+
for nmap_host in nmap_report.hosts:
13+
rv = store_reportitem(nmap_host, database, index)
14+
if rv is False:
15+
print("Failed to store host {0} in "
16+
"elasticsearch".format(nmap_host.address))
17+
rval = False
18+
19+
return rval
20+
21+
22+
def get_os(nmap_host):
23+
rval = {'vendor': 'unknown', 'product': 'unknown'}
24+
if nmap_host.is_up() and nmap_host.os_fingerprinted:
25+
cpelist = nmap_host.os.os_cpelist()
26+
if len(cpelist):
27+
mcpe = cpelist.pop()
28+
rval.update({'vendor': mcpe.get_vendor(),
29+
'product': mcpe.get_product()})
30+
return rval
31+
32+
33+
def get_geoip_code(address):
34+
gi = pygeoip.GeoIP('/usr/share/GeoIP/GeoIP.dat')
35+
return gi.country_code_by_addr(address)
36+
37+
38+
def store_reportitem(nmap_host, database, index):
39+
host_keys = ["starttime", "endtime", "address", "hostnames",
40+
"ipv4", "ipv6", "mac", "status"]
41+
jhost = {}
42+
for hkey in host_keys:
43+
if hkey == "starttime" or hkey == "endtime":
44+
val = getattr(nmap_host, hkey)
45+
jhost[hkey] = datetime.fromtimestamp(int(val) if len(val) else 0)
46+
else:
47+
jhost[hkey] = getattr(nmap_host, hkey)
48+
49+
jhost.update({'country': get_geoip_code(nmap_host.address)})
50+
jhost.update(get_os(nmap_host))
51+
for nmap_service in nmap_host.services:
52+
reportitems = get_item(nmap_service)
53+
54+
for ritem in reportitems:
55+
ritem.update(jhost)
56+
database.index(index=index,
57+
doc_type="NmapItem",
58+
body=ritem)
59+
return jhost
60+
61+
62+
def get_item(nmap_service):
63+
service_keys = ["port", "protocol", "state"]
64+
ritems = []
65+
66+
# create report item for basic port scan
67+
jservice = {}
68+
for skey in service_keys:
69+
jservice[skey] = getattr(nmap_service, skey)
70+
jservice['type'] = 'port-scan'
71+
jservice['service'] = nmap_service.service
72+
jservice['service-data'] = nmap_service.banner
73+
ritems.append(jservice)
74+
75+
# create report items from nse script output
76+
for nse_item in nmap_service.scripts_results:
77+
jnse = {}
78+
for skey in service_keys:
79+
jnse[skey] = getattr(nmap_service, skey)
80+
jnse['type'] = 'nse-script'
81+
jnse['service'] = nse_item['id']
82+
jnse['service-data'] = nse_item['output']
83+
ritems.append(jnse)
84+
85+
return ritems
86+
87+
88+
xmlscans = ['../libnmap/test/files/1_hosts.xml',
89+
'../libnmap/test/files/full_sudo6.xml',
90+
'/vagrant/nmap_switches.xml',
91+
'/vagrant/nmap-5hosts.xml']
92+
93+
for xmlscan in xmlscans:
94+
nmap_report = NmapParser.parse_fromfile(xmlscan)
95+
96+
if nmap_report:
97+
rep_date = datetime.fromtimestamp(int(nmap_report.started))
98+
index = "nmap-{0}".format(rep_date.strftime('%Y-%m-%d'))
99+
db = Elasticsearch()
100+
j = store_report(nmap_report, db, index)

examples/es_plugin.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env python
2+
3+
from libnmap.parser import NmapParser
4+
from libnmap.reportjson import ReportDecoder
5+
from libnmap.plugins.es import NmapElasticsearchPlugin
6+
from datetime import datetime
7+
import json
8+
9+
nmap_report = NmapParser.parse_fromfile('libnmap/test/files/1_hosts.xml')
10+
mindex = datetime.fromtimestamp(nmap_report.started).strftime('%Y-%m-%d')
11+
db = NmapElasticsearchPlugin(index=mindex)
12+
dbid = db.insert(nmap_report)
13+
nmap_json = db.get(dbid)
14+
15+
nmap_obj = json.loads(json.dumps(nmap_json), cls=ReportDecoder)
16+
print(nmap_obj)
17+
#print(db.getall())
18+

examples/kibanalibnmap.png

314 KB
Loading

libnmap/objects/os.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,13 @@ def osclass(self, min_accuracy=90):
376376
os_array.append(_ftstr)
377377
return os_array
378378

379+
def os_cpelist(self):
380+
cpelist = []
381+
for _osmatch in self.osmatches:
382+
for oclass in _osmatch.osclasses:
383+
cpelist.extend(oclass.cpelist)
384+
return cpelist
385+
379386
def __repr__(self):
380387
rval = ""
381388
for _osmatch in self.osmatches:

libnmap/plugins/es.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import json
4+
from libnmap.reportjson import ReportEncoder
5+
from libnmap.plugins.backendplugin import NmapBackendPlugin
6+
from elasticsearch import Elasticsearch
7+
from datetime import datetime
8+
9+
10+
class NmapElasticsearchPlugin(NmapBackendPlugin):
11+
"""
12+
This class enables the user to store and manipulate nmap reports \
13+
in a elastic search db.
14+
"""
15+
def __init__(self, index=None):
16+
if index is None:
17+
self.index = "nmap.{0}".format(datetime.now().strftime('%Y-%m-%d'))
18+
else:
19+
self.index = index
20+
self._esapi = Elasticsearch()
21+
22+
def insert(self, report, doc_type=None):
23+
"""
24+
insert NmapReport in the backend
25+
:param NmapReport:
26+
:return: str the ident of the object in the backend for
27+
future usage
28+
or None
29+
"""
30+
if doc_type is None:
31+
doc_type = 'NmapReport'
32+
j = json.dumps(report, cls=ReportEncoder)
33+
res = self._esapi.index(
34+
index=self.index,
35+
doc_type=doc_type,
36+
body=json.loads(j))
37+
rc = res['_id']
38+
return rc
39+
40+
def delete(self, id):
41+
"""
42+
delete NmapReport if the backend
43+
:param id: str
44+
"""
45+
raise NotImplementedError
46+
47+
def get(self, id):
48+
"""
49+
retreive a NmapReport from the backend
50+
:param id: str
51+
:return: NmapReport
52+
"""
53+
res = self._esapi.get(index=self.index,
54+
doc_type="NmapReport",
55+
id=id)['_source']
56+
return res
57+
58+
def getall(self, filter=None):
59+
"""
60+
:return: collection of tuple (id,NmapReport)
61+
:param filter: Nice to have implement a filter capability
62+
"""
63+
rsearch = self._esapi.search(index=self.index,
64+
body={"query": {"match_all": {}}})
65+
print("--------------------")
66+
print(type(rsearch))
67+
print(rsearch)
68+
print("------------")

0 commit comments

Comments
 (0)