Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
from collections import defaultdict
from typing import Dict, Any, Collection, List, Set
import xmltodict
from internal_types import ScanResult, Vuln
__all__ = ['FlanXmlParser']
class FlanXmlParser:
"""
NMAP XML file reader and contents parser
"""
def __init__(self):
self.results = defaultdict(ScanResult)
self.vulnerable_services = [] # type: List[str]
@property
def vulnerable_dict(self) -> Dict[str, ScanResult]:
"""
:return: Map {app_name -> scan result} for vulnerable services
"""
return {service: self.results[service] for service in self.vulnerable_services}
@property
def non_vulnerable_dict(self) -> Dict[str, ScanResult]:
"""
:return: Map {app_name -> scan result} for services without detected vulnerabilities
"""
return {service: self.results[service] for service in self.non_vuln_services}
@property
def non_vuln_services(self) -> Set[str]:
"""
:return: App names for services without detected vulnerabilities
"""
return set(self.results) - set(self.vulnerable_services)
def parse(self, data: Dict[str, Any]):
"""
Parse xmltodict output and fill internal collections
:param data: xmltodict output
"""
hosts = data['nmaprun']['host']
if isinstance(hosts, list):
for h in hosts:
self.parse_host(h)
else:
self.parse_host(hosts)
def parse_vuln(self, app_name: str, vuln: Collection[Dict[str, Any]]):
vuln_name = ''
severity = ''
vuln_type = ''
for field in vuln:
if field['@key'] == 'cvss':
severity = float(field['#text'])
elif field['@key'] == 'id':
vuln_name = field['#text']
elif field['@key'] == 'type':
vuln_type = field['#text']
self.results[app_name].vulns.append(Vuln(vuln_name, vuln_type, severity))
def parse_script(self, app_name: str, script: Dict[str, Any]):
self.vulnerable_services.append(app_name)
script_table = script['table']['table']
if isinstance(script_table, list):
for vuln in script_table:
self.parse_vuln(app_name, vuln['elem'])
else:
self.parse_vuln(app_name, script_table['elem'])
def parse_port(self, ip_addr: str, port: Dict[str, Any]):
if port['state']['@state'] == 'closed':
return
app_name = self.get_app_name(port['service'])
port_num = port['@portid']
self.results[app_name].locations[ip_addr].append(port_num)
if 'script' in port:
scripts = port['script']
if isinstance(scripts, list):
for s in scripts:
if s['@id'] == 'vulners':
self.parse_script(app_name, s)
else:
if scripts['@id'] == 'vulners':
self.parse_script(app_name, scripts)
def parse_host(self, host: Dict[str, Any]):
ip_addr = host['address']['@addr']
if host['status']['@state'] == 'up' and 'port' in host['ports']:
ports = host['ports']['port']
if isinstance(ports, list):
for p in ports:
self.parse_port(ip_addr, p)
else:
self.parse_port(ip_addr, ports)
def read_xml_file(self, path: str) -> Dict[str, Any]:
"""
Read file and convert to dictionary. To read raw contents use `read_xml_contents`
:param path: path to .xml file
:return: parsed contents
"""
with open(path) as f:
contents = f.read()
return self.read_xml_contents(contents)
@staticmethod
def get_app_name(service: Dict[str, Any]) -> str:
app_name = ''
if '@product' in service:
app_name += service['@product'] + ' '
if '@version' in service:
app_name += service['@version'] + ' '
elif '@name' in service:
app_name += service['@name'] + ' '
if 'cpe' in service:
if isinstance(service['cpe'], list):
for cpe in service['cpe']:
app_name += '(' + cpe + ') '
else:
app_name += '(' + service['cpe'] + ') '
return app_name
@staticmethod
def read_xml_contents(contents: str) -> Dict[str, Any]:
return xmltodict.parse(contents)