Mercurial > farol > farolluz
comparison farolluz/parsers/cvrf.py @ 0:e18b61a73a68
Initial Release
author | Benoît Allard <benoit.allard@greenbone.net> |
---|---|
date | Tue, 23 Sep 2014 15:19:14 +0200 |
parents | |
children | d47e1164740f |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:e18b61a73a68 |
---|---|
1 # -*- coding: utf-8 -*- | |
2 # Description: | |
3 # Methods for parsing CVRF documents | |
4 # | |
5 # Authors: | |
6 # BenoƮt Allard <benoit.allard@greenbone.net> | |
7 # | |
8 # Copyright: | |
9 # Copyright (C) 2014 Greenbone Networks GmbH | |
10 # | |
11 # This program is free software; you can redistribute it and/or | |
12 # modify it under the terms of the GNU General Public License | |
13 # as published by the Free Software Foundation; either version 2 | |
14 # of the License, or (at your option) any later version. | |
15 # | |
16 # This program is distributed in the hope that it will be useful, | |
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 # GNU General Public License for more details. | |
20 # | |
21 # You should have received a copy of the GNU General Public License | |
22 # along with this program; if not, write to the Free Software | |
23 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
24 | |
25 """\ | |
26 Methods for parsing of CVRF Documents | |
27 """ | |
28 | |
29 from __future__ import print_function | |
30 | |
31 import re | |
32 import textwrap | |
33 import xml.etree.ElementTree as ET | |
34 from datetime import datetime, timedelta | |
35 | |
36 try: | |
37 from datetime import timezone | |
38 except ImportError: | |
39 from ..py2 import FixedTimeZone as timezone | |
40 | |
41 from ..cvrf import (CVRF, CVRFPublisher, CVRFTracking, CVRFRevision, CVRFNote, | |
42 CVRFAcknowledgment, CVRFProductBranch, CVRFFullProductName, CVRFGenerator, | |
43 CVRFRelationship, CVRFVulnerability, CVRFVulnerabilityID, CVRFThreat, | |
44 CVRFProductStatus, CVRFCVSSSet, CVRFReference, CVRFRemediation, CVRFGroup, | |
45 CVRFInvolvement, CVRFCWE, CVRFTrackingID) | |
46 | |
47 NAMESPACES = { | |
48 'cvrf': "http://www.icasi.org/CVRF/schema/cvrf/1.1", | |
49 'prod': "http://www.icasi.org/CVRF/schema/prod/1.1", | |
50 'vuln': "http://www.icasi.org/CVRF/schema/vuln/1.1", | |
51 'xml': "http://www.w3.org/XML/1998/namespace", | |
52 } | |
53 | |
54 | |
55 def UN(ns, name): | |
56 """ UN for Universal Name """ | |
57 return "{%s}%s" % (NAMESPACES[ns], name) | |
58 | |
59 | |
60 def parseVersion(string): | |
61 return tuple(int(i) for i in string.split('.')) | |
62 | |
63 | |
64 def parseDate(string): | |
65 m = re.match('(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:([+-])(\d{2}):(\d{2})|(Z))?', string) | |
66 if (m.group(7) is None) or (m.group(7) == 'Z'): | |
67 tzhours = 0 | |
68 tzmin = 0 | |
69 else: | |
70 tzhours = int(m.group(8)) | |
71 if m.group(7) == '-': | |
72 tzhours = - tzhours | |
73 tzmin = int(m.group(9)) | |
74 return datetime(int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4)), int(m.group(5)), int(m.group(6)), tzinfo=timezone(timedelta(hours=tzhours, minutes=tzmin))) | |
75 | |
76 | |
77 def parseNote(elem): | |
78 return CVRFNote( | |
79 elem.attrib['Type'], | |
80 int(elem.attrib['Ordinal']), | |
81 textwrap.dedent(elem.text).strip(), | |
82 elem.attrib.get('Title'), | |
83 elem.attrib.get('Audience') | |
84 ) | |
85 | |
86 | |
87 def parseReference(elem, ns='cvrf'): | |
88 """ ns is the current namespace """ | |
89 return CVRFReference( | |
90 elem.findtext(UN(ns, 'URL')).strip(), | |
91 textwrap.dedent(elem.findtext(UN(ns, 'Description'))).strip(), | |
92 elem.attrib.get('Type') | |
93 ) | |
94 | |
95 | |
96 def parseAcknowledgment(elem, ns='cvrf'): | |
97 return CVRFAcknowledgment( | |
98 elem.findtext(UN(ns, 'Name')), | |
99 elem.findtext(UN(ns, 'Organization')), | |
100 elem.findtext(UN(ns, 'Description')), | |
101 elem.findtext(UN(ns, 'URL')), | |
102 ) | |
103 | |
104 | |
105 def parseFullProductName(elem, parent): | |
106 return CVRFFullProductName( | |
107 elem.attrib['ProductID'], | |
108 elem.text.strip(), | |
109 parent, | |
110 cpe=elem.attrib.get('CPE') | |
111 ) | |
112 | |
113 | |
114 def parseProdBranch(elem, ptree, parentbranch=None): | |
115 """ Recursively parses the branches and the terminal productnames """ | |
116 fpncvrf = elem.find(UN('prod', 'FullProductName')) | |
117 if (parentbranch is not None) and (fpncvrf is not None): | |
118 # Don't process the products at the root of the tree | |
119 prod = parseFullProductName(fpncvrf, parentbranch) | |
120 ptree.addProduct(prod) | |
121 | |
122 if parentbranch is None: | |
123 parentbranch = ptree | |
124 for brcvrf in elem.findall(UN('prod', 'Branch')): | |
125 br = CVRFProductBranch(brcvrf.attrib['Type'], brcvrf.attrib['Name'], parentbranch) | |
126 # And go into recursion ... | |
127 br._childs = list(parseProdBranch(brcvrf, ptree, br)) | |
128 yield br | |
129 | |
130 | |
131 def parseVulnerability(elem): | |
132 vuln = CVRFVulnerability(int(elem.attrib['Ordinal'])) | |
133 | |
134 xmltitle = elem.findtext(UN('vuln', 'Title')) | |
135 if xmltitle is not None: | |
136 vuln.setTitle(xmltitle.strip()) | |
137 | |
138 xmlID = elem.find(UN('vuln', 'ID')) | |
139 if xmlID is not None: | |
140 vuln.setID(CVRFVulnerabilityID(xmlID.attrib['SystemName'], xmlID.text.strip())) | |
141 | |
142 for xmlnote in elem.findall('/'.join([UN('vuln', 'Notes'), UN('vuln', 'Note')])): | |
143 vuln.addNote(parseNote(xmlnote)) | |
144 | |
145 xmldiscoverydate = elem.findtext(UN('vuln', 'DiscoveryDate')) | |
146 if xmldiscoverydate is not None: | |
147 vuln.setDiscoveryDate(parseDate(xmldiscoverydate)) | |
148 xmlreleasedate = elem.findtext(UN('vuln', 'ReleaseDate')) | |
149 if xmlreleasedate is not None: | |
150 vuln.setReleaseDate(parseDate(xmlreleasedate)) | |
151 | |
152 for xmlinv in elem.findall('/'.join([UN('vuln', 'Involvements'), UN('vuln', 'Involvement')])): | |
153 involvement = CVRFInvolvement( | |
154 xmlinv.attrib['Party'], | |
155 xmlinv.attrib['Status'] | |
156 ) | |
157 xmldescr = xmlinv.findtext(UN('vuln', 'Description')) | |
158 if xmldescr is not None: | |
159 involvement.setDescription(textwrap.dedent(xmldescr).strip()) | |
160 vuln.addInvolvement(involvement) | |
161 | |
162 xmlcve = elem.findtext(UN('vuln', 'CVE')) | |
163 if xmlcve is not None: | |
164 vuln.setCVE(xmlcve.strip()) | |
165 | |
166 for xmlcwe in elem.findall(UN('vuln', 'CWE')): | |
167 vuln.addCWE(CVRFCWE( | |
168 xmlcwe.attrib['ID'], | |
169 xmlcwe.text.strip() | |
170 )) | |
171 | |
172 for xmlstatus in elem.findall('/'.join([UN('vuln', 'ProductStatuses'), UN('vuln', 'Status')])): | |
173 status = CVRFProductStatus(xmlstatus.attrib['Type']) | |
174 for xmlproductid in xmlstatus.findall(UN('vuln', 'ProductID')): | |
175 status.addProductID(xmlproductid.text.strip()) | |
176 | |
177 vuln.addProductStatus(status) | |
178 | |
179 for xmlthreat in elem.findall('/'.join([UN('vuln', 'Threats'), UN('vuln', 'Threat')])): | |
180 threat = CVRFThreat( | |
181 xmlthreat.attrib['Type'], | |
182 textwrap.dedent(xmlthreat.findtext(UN('vuln', 'Description'))).strip() | |
183 ) | |
184 xmldate = xmlthreat.findtext(UN('vuln', 'Date')) | |
185 if xmldate is not None: | |
186 threat.setDate(parseDate(xmldate)) | |
187 for xmlpid in xmlthreat.findall(UN('vuln', 'ProductID')): | |
188 threat.addProductID(xmlpid.text.strip()) | |
189 for xmlgid in xmlthreat.findall(UN('vuln', 'GroupID')): | |
190 threat.addGroupID(xmlgid.text.strip()) | |
191 | |
192 vuln.addThreat(threat) | |
193 | |
194 for xmlcvss in elem.findall('/'.join([UN('vuln', 'CVSSScoreSets'), UN('vuln', 'ScoreSet')])): | |
195 cvss_set = CVRFCVSSSet(float(xmlcvss.findtext(UN('vuln', 'BaseScore')).strip())) | |
196 xmltempscore = xmlcvss.findtext(UN('vuln', 'TemporalScore')) | |
197 if xmltempscore is not None: | |
198 cvss_set.setTemporalScore(float(xmltempscore.strip())) | |
199 xmlenvscore = xmlcvss.findtext(UN('vuln', 'EnvironmentalScore')) | |
200 if xmlenvscore is not None: | |
201 cvss_set.setEnvironmentalScore(float(xmlenvscore.strip())) | |
202 xmlvector = xmlcvss.findtext(UN('vuln', 'Vector')) | |
203 if xmlvector is not None: | |
204 cvss_set.setVector(xmlvector.strip()) | |
205 for xmlprodid in xmlcvss.findall(UN('vuln', 'ProductID')): | |
206 cvss_set.addProductID(xmlprodid.text.strip()) | |
207 | |
208 vuln.addCVSSSet(cvss_set) | |
209 | |
210 for xmlremediation in elem.findall('/'.join([UN('vuln', 'Remediations'), UN('vuln', 'Remediation')])): | |
211 remediation = CVRFRemediation( | |
212 xmlremediation.attrib['Type'], | |
213 textwrap.dedent(xmlremediation.findtext(UN('vuln', 'Description'))).strip() | |
214 ) | |
215 xmldate = xmlremediation.findtext(UN('vuln', 'Date')) | |
216 if xmldate is not None: | |
217 remediation.setDate(parseDate(xmldate)) | |
218 xmlentitlement = xmlremediation.findtext(UN('vuln', 'Entitlement')) | |
219 if xmlentitlement is not None: | |
220 remediation.setEntitlement(textwrap.dedent(xmlentitlement).strip()) | |
221 xmlurl = xmlremediation.findtext(UN('vuln', 'URL')) | |
222 if xmlurl is not None: | |
223 remediation.setURL(xmlurl.strip()) | |
224 for xmlpid in xmlremediation.findall(UN('vuln', 'ProductID')): | |
225 remediation.addProductID(xmlpid.text.strip()) | |
226 for xmlgid in xmlremediation.findall(UN('vuln', 'GroupID')): | |
227 remediation.addGroupID(xmlgid.text.strip()) | |
228 | |
229 vuln.addRemediation(remediation) | |
230 | |
231 for xmlref in elem.findall('/'.join([UN('vuln', 'References'), UN('vuln', 'Reference')])): | |
232 vuln.addReference(parseReference(xmlref, 'vuln')) | |
233 | |
234 for xmlack in elem.findall('/'.join([UN('vuln', 'Acknowledgments'), UN('vuln', 'Acknowledgment')])): | |
235 vuln.addAcknowledgment(parseAcknowledgment(xmlack, 'vuln')) | |
236 | |
237 return vuln | |
238 | |
239 | |
240 def parse(xml): | |
241 if hasattr(xml, 'read'): | |
242 xml = xml.read() | |
243 cvrfdoc = ET.fromstring(xml) | |
244 if cvrfdoc.tag != UN('cvrf', 'cvrfdoc'): | |
245 raise ValueError('Not a CVRF document !') | |
246 doc = CVRF( | |
247 cvrfdoc.findtext(UN('cvrf', 'DocumentTitle')).strip(), | |
248 cvrfdoc.findtext(UN('cvrf', 'DocumentType')).strip() | |
249 ) | |
250 cvrfpub = cvrfdoc.find(UN('cvrf', 'DocumentPublisher')) | |
251 pub = CVRFPublisher(cvrfpub.attrib['Type'], cvrfpub.attrib.get('VendorID')) | |
252 doc.setPublisher(pub) | |
253 contact = cvrfpub.find(UN('cvrf', 'ContactDetails')) | |
254 if contact is not None: | |
255 pub.setContact(contact.text.strip()) | |
256 authority = cvrfpub.find(UN('cvrf', 'IssuingAuthority')) | |
257 if authority is not None: | |
258 pub.setAuthority(authority.text.strip()) | |
259 cvrftracking = cvrfdoc.find(UN('cvrf', 'DocumentTracking')) | |
260 identification = CVRFTrackingID( | |
261 cvrftracking.findtext('/'.join([UN('cvrf', 'Identification'), UN('cvrf', 'ID')])).strip() | |
262 ) | |
263 for cvrfalias in cvrftracking.findall('/'.join([UN('cvrf', 'Identification'), UN('cvrf', 'Alias')])): | |
264 identification.addAlias(cvrfalias.text.strip()) | |
265 tracking = CVRFTracking( | |
266 identification, | |
267 cvrftracking.findtext(UN('cvrf', 'Status')).strip(), | |
268 parseVersion(cvrftracking.findtext(UN('cvrf', 'Version')).strip()), | |
269 parseDate(cvrftracking.findtext(UN('cvrf', 'InitialReleaseDate')).strip()), | |
270 parseDate(cvrftracking.findtext(UN('cvrf', 'CurrentReleaseDate')).strip()) | |
271 ) | |
272 doc.setTracking(tracking) | |
273 for cvrfrev in cvrftracking.findall('/'.join([UN('cvrf', 'RevisionHistory'), UN('cvrf', 'Revision')])): | |
274 rev = CVRFRevision( | |
275 parseVersion(cvrfrev.findtext(UN('cvrf', 'Number')).strip()), | |
276 parseDate(cvrfrev.findtext(UN('cvrf', 'Date')).strip()), | |
277 cvrfrev.findtext(UN('cvrf', 'Description')).strip(), | |
278 ) | |
279 tracking.addRevision(rev) | |
280 | |
281 xmlgenerator = cvrftracking.find(UN('cvrf', 'Generator')) | |
282 if xmlgenerator is not None: | |
283 generator = CVRFGenerator() | |
284 xmlengine = xmlgenerator.findtext(UN('cvrf', 'Engine')) | |
285 if xmlengine is not None: | |
286 generator.setEngine(xmlengine.strip()) | |
287 xmldate = xmlgenerator.findtext(UN('cvrf', 'Date')) | |
288 if xmldate is not None: | |
289 generator.setDate(parseDate(xmldate.strip())) | |
290 tracking.setGenerator(generator) | |
291 | |
292 for cvrfnote in cvrfdoc.findall('/'.join([UN('cvrf', 'DocumentNotes'), UN('cvrf', 'Note')])): | |
293 doc.addNote(parseNote(cvrfnote)) | |
294 | |
295 distr = cvrfdoc.findtext(UN('cvrf', 'DocumentDistribution')) | |
296 if distr is not None: | |
297 doc.setDistribution(textwrap.dedent(distr).strip()) | |
298 | |
299 # This is in a quite free format, not sure how to do something with it ... | |
300 xmlaggsev = cvrfdoc.find(UN('cvrf', 'AggregateSeverity')) | |
301 | |
302 for xmlref in cvrfdoc.findall('/'.join([UN('cvrf', 'DocumentReferences'), UN('cvrf', 'Reference')])): | |
303 doc.addReference(parseReference(xmlref)) | |
304 | |
305 for cvrfack in cvrfdoc.findall('/'.join([UN('cvrf', 'Acknowledgments'), UN('cvrf', 'Acknowledgment')])): | |
306 doc.addAcknowledgment(parseAcknowledgment(cvrfack)) | |
307 | |
308 # --- The ProductTree | |
309 | |
310 cvrfptree = cvrfdoc.find(UN('prod', 'ProductTree')) | |
311 if cvrfptree is not None: | |
312 producttree = doc.createProductTree() | |
313 for branch in parseProdBranch(cvrfptree, producttree): | |
314 producttree.addBranch(branch) | |
315 | |
316 for product in cvrfptree.findall(UN('prod', 'FullProductName')): | |
317 producttree.addProduct(parseFullProductName(product, producttree)) | |
318 | |
319 for cvrfrel in cvrfptree.findall(UN('prod', 'Relationship')): | |
320 rel = CVRFRelationship( | |
321 cvrfrel.attrib['ProductReference'], | |
322 cvrfrel.attrib['RelationType'], | |
323 cvrfrel.attrib['RelatesToProductReference'] | |
324 ) | |
325 producttree.addRelationship(rel) | |
326 producttree.addProduct(parseFullProductName(cvrfrel.find(UN('prod', 'FullProductName')), rel)) | |
327 | |
328 for xmlgroup in cvrfptree.findall('/'.join([UN('prod', 'ProductGroups'), UN('prod', 'Group')])): | |
329 group = CVRFGroup(xmlgroup.attrib['GroupID']) | |
330 xmldescr = xmlgroup.findtext(UN('prod', 'Description')) | |
331 if xmldescr is not None: | |
332 group.setDescription(textwrap.dedent(xmldescr).strip()) | |
333 for xmlpid in xmlgroup.findall(UN('prod', 'ProductID')): | |
334 group.addProductID(xmlpid.text.strip()) | |
335 producttree.addGroup(group) | |
336 | |
337 # --- The Vulnerabilities | |
338 | |
339 for cvrfvuln in cvrfdoc.findall(UN('vuln', 'Vulnerability')): | |
340 doc.addVulnerability(parseVulnerability(cvrfvuln)) | |
341 | |
342 return doc | |
343 | |
344 | |
345 if __name__ == "__main__": | |
346 import sys | |
347 with open(sys.argv[1], 'rt') as f: | |
348 cvrf = parse(f) | |
349 cvrf.validate() | |
350 print(cvrf) | |
351 print(cvrf.getHighestCVSS()._vector) | |
352 print(cvrf.getProductList()) | |
353 print(cvrf._producttree._branches) | |
354 # print(cvrf._producttree._branches[0]._childs) |