benoit@0: # -*- coding: utf-8 -*- benoit@0: # benoit@0: # Authors: benoit@0: # BenoƮt Allard benoit@0: # benoit@0: # Copyright: benoit@0: # Copyright (C) 2014 Greenbone Networks GmbH benoit@0: # benoit@0: # This program is free software; you can redistribute it and/or benoit@0: # modify it under the terms of the GNU General Public License benoit@0: # as published by the Free Software Foundation; either version 2 benoit@0: # of the License, or (at your option) any later version. benoit@0: # benoit@0: # This program is distributed in the hope that it will be useful, benoit@0: # but WITHOUT ANY WARRANTY; without even the implied warranty of benoit@0: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the benoit@0: # GNU General Public License for more details. benoit@0: # benoit@0: # You should have received a copy of the GNU General Public License benoit@0: # along with this program; if not, write to the Free Software benoit@0: # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. benoit@0: benoit@0: """\ benoit@26: Vulnerability Objects related to CVRF Documents benoit@0: """ benoit@0: benoit@26: from .common import ValidationError benoit@26: from .document import CVRFPublisher benoit@0: benoit@0: benoit@0: class CVRFVulnerabilityID(object): benoit@0: def __init__(self, systemname, value): benoit@0: self._systemname = systemname benoit@0: self._value = value benoit@0: benoit@0: def validate(self): benoit@0: if not self._systemname: benoit@0: raise ValidationError('A Vulnerability ID must have a System Name') benoit@0: if not self._value: benoit@0: raise ValidationError('A Vulnerability ID must have a value') benoit@0: benoit@0: benoit@0: class CVRFVulnerability(object): benoit@0: def __init__(self, ordinal): benoit@0: self._ordinal = ordinal benoit@0: self._title = None benoit@0: self._id = None benoit@0: self._notes = [] benoit@0: self._discoverydate = None benoit@0: self._releasedate = None benoit@0: self._involvements = [] benoit@0: self._cve = None benoit@0: self._cwes = [] benoit@0: self._productstatuses = [] benoit@0: self._threats = [] benoit@0: self._cvsss = [] benoit@0: self._remediations = [] benoit@0: self._references = [] benoit@0: self._acknowledgments = [] benoit@0: benoit@0: def setTitle(self, title): benoit@0: self._title = title benoit@0: benoit@0: def setID(self, _id): benoit@0: self._id = _id benoit@0: benoit@0: def addNote(self, note): benoit@0: self._notes.append(note) benoit@0: benoit@0: def setDiscoveryDate(self, date): benoit@0: self._discoverydate = date benoit@0: benoit@0: def setReleaseDate(self, date): benoit@0: self._releasedate = date benoit@0: benoit@0: def addInvolvement(self, involvement): benoit@0: self._involvements.append(involvement) benoit@0: benoit@0: def setCVE(self, cve): benoit@0: self._cve = cve benoit@0: benoit@0: def addCWE(self, cwe): benoit@0: self._cwes.append(cwe) benoit@0: benoit@0: def addProductStatus(self, productstatus): benoit@0: self._productstatuses.append(productstatus) benoit@0: benoit@0: def addThreat(self, threat): benoit@0: self._threats.append(threat) benoit@0: benoit@0: def addCVSSSet(self, cvss_set): benoit@0: self._cvsss.append(cvss_set) benoit@0: benoit@0: def addRemediation(self, remediation): benoit@0: self._remediations.append(remediation) benoit@0: benoit@0: def addReference(self, ref): benoit@0: self._references.append(ref) benoit@0: benoit@0: def addAcknowledgment(self, ack): benoit@0: self._acknowledgments.append(ack) benoit@0: benoit@0: def getTitle(self): benoit@0: """ return something that can be used as a title """ benoit@0: if self._title: benoit@0: if self._id: benoit@0: return "%s (%s)" % (self._title, self._id._value) benoit@0: return self._title benoit@0: if self._id: benoit@0: return self._id._value benoit@0: return "#%d" % self._ordinal benoit@0: benoit@7: def getNote(self, ordinal): benoit@7: for note in self._notes: benoit@7: if note._ordinal == ordinal: benoit@7: return note benoit@7: return None benoit@7: benoit@17: def mentionsProdId(self, productid): benoit@17: """ Returns in which sub element, self is mentioning the productid """ benoit@17: for category in (self._productstatuses, self._threats, self._cvsss, self._remediations): benoit@17: for subelem in category: benoit@17: if productid in subelem._productids: benoit@17: yield subelem benoit@17: benoit@17: def isMentioningProdId(self, productid): benoit@17: """ Returns if self is mentioning the productid """ benoit@17: for e in self.mentionsProdId(productid): benoit@17: # We only need to know if the generator yield at least one elem. benoit@17: return True benoit@17: return False benoit@17: benoit@17: def mentionsGroupId(self, groupid): benoit@17: for category in (self._threats, self._remediations): benoit@17: for subelem in category: benoit@17: if groupid in subelem._groupids: benoit@17: yield subelem benoit@17: benoit@17: def isMentioningGroupId(self, groupids): benoit@17: """ Make sure you call this with a list (not a generator or a tuple) benoit@17: when wished """ benoit@17: if not isinstance(groupids, list): benoit@17: groupids = [groupids] benoit@17: for groupid in groupids: benoit@17: for _ in self.mentionsGroupId(groupid): benoit@17: # We only need to know if the generator yield at least one elem. benoit@17: return True benoit@17: return False benoit@17: benoit@0: def validate(self, productids, groupids): benoit@0: if not self._ordinal: benoit@0: raise ValidationError('A Vulnerability must have an ordinal') benoit@0: if self._id is not None: benoit@0: self._id.validate() benoit@0: ordinals = set() benoit@0: for note in self._notes: benoit@0: note.validate() benoit@0: if note._ordinal in ordinals: benoit@0: raise ValidationError('Vulnerability Note Ordinal %d duplicated' % note._ordinal) benoit@0: ordinals.add(note._ordinal) benoit@0: for involvement in self._involvements: benoit@0: involvement.validate() benoit@0: for cwe in self._cwes: benoit@0: cwe.validate() benoit@0: for status in self._productstatuses: benoit@0: status.validate(productids) benoit@13: pids = set() benoit@13: for status in self._productstatuses: benoit@13: for pid in status._productids: benoit@13: if pid in pids: benoit@13: raise ValidationError('ProductID %s mentionned in two different ProductStatuses for Vulnerability %d' % (pid, self._ordinal)) benoit@13: pids.add(pid) benoit@0: for threat in self._threats: benoit@0: threat.validate(productids, groupids) benoit@0: for cvss in self._cvsss: benoit@0: cvss.validate(productids) benoit@14: pids = set() benoit@13: for cvss in self._cvsss: benoit@13: for pid in (cvss._productids or productids): benoit@13: if pid in pids: benoit@13: raise ValidationError('ProductID %s mentionned in two different CVSS Score Sets for Vulnerability %d' % (pid, self._ordinal)) benoit@13: pids.add(pid) benoit@0: for remediation in self._remediations: benoit@0: remediation.validate(productids, groupids) benoit@0: for reference in self._references: benoit@0: reference.validate() benoit@0: for acknowledgment in self._acknowledgments: benoit@0: acknowledgment.validate() benoit@0: benoit@0: benoit@0: class CVRFInvolvement(object): benoit@0: PARTIES = CVRFPublisher.TYPES benoit@0: STATUSES = ('Open', 'Disputed', 'In Progress', 'Completed', benoit@0: 'Contact Attempted', 'Not Contacted') benoit@0: def __init__(self, party, status): benoit@0: self._party = party benoit@0: self._status = status benoit@0: self._description = None benoit@0: benoit@0: def setDescription(self, description): benoit@0: self._description = description benoit@0: benoit@0: def getTitle(self): benoit@0: return "From %s: %s" % (self._party, self._status) benoit@0: benoit@0: def validate(self): benoit@0: if not self._party: benoit@0: raise ValidationError('An Involvement must have a Party') benoit@0: if self._party not in self.PARTIES: benoit@0: raise ValidationError("An Involvement's Party must be one of %s" % ', '.join(self.PARTIES)) benoit@0: if not self._status: benoit@0: raise ValidationError('An Involvement must have a Status') benoit@0: if self._status not in self.STATUSES: benoit@0: raise ValidationError("An Involvement's Status must be one of %s" % ', '.join(self.STATUSES)) benoit@0: benoit@0: benoit@0: class CVRFCWE(object): benoit@0: def __init__(self, _id, value): benoit@0: self._id = _id benoit@0: self._value = value benoit@0: benoit@0: def validate(self): benoit@0: if not self._id: benoit@0: raise ValidationError('A CWE must have an ID') benoit@0: if not self._value: benoit@0: raise ValidationError('A CWE must have a description') benoit@0: benoit@0: benoit@0: class CVRFProductStatus(object): benoit@0: TYPES = ('First Affected', 'Known Affected', 'Known Not Affected', benoit@0: 'First Fixed', 'Fixed', 'Recommended', 'Last Affected') benoit@17: NAME = "Product Status" benoit@0: def __init__(self, _type): benoit@0: self._type = _type benoit@0: self._productids = [] benoit@0: benoit@0: def addProductID(self, productid): benoit@0: self._productids.append(productid) benoit@0: benoit@0: def getTitle(self): benoit@0: return "%s: %d products" % (self._type, len(self._productids)) benoit@0: benoit@0: def validate(self, productids): benoit@0: if not self._type: benoit@0: raise ValidationError('A Product Status must have a Type') benoit@0: if self._type not in self.TYPES: benoit@0: raise ValidationError("A Product Status' Type must be one of %s" % ', '.join(self.TYPES)) benoit@0: if len(self._productids) < 1: benoit@0: raise ValidationError('A Product Status must mention at least one Product') benoit@0: for productid in self._productids: benoit@0: if productid not in productids: benoit@0: raise ValidationError('Unknown ProductID: %s' % productid) benoit@0: benoit@0: benoit@0: class CVRFThreat(object): benoit@0: TYPES = ('Impact', 'Exploit Status', 'Target Set') benoit@17: NAME = "Threat" benoit@0: def __init__(self, _type, description): benoit@0: self._type = _type benoit@0: self._description = description benoit@0: self._date = None benoit@0: self._productids = [] benoit@0: self._groupids = [] benoit@0: benoit@0: def setDate(self, date): benoit@0: self._date = date benoit@0: benoit@0: def addProductID(self, productid): benoit@0: self._productids.append(productid) benoit@0: benoit@0: def addGroupID(self, groupid): benoit@0: self._groupids.append(groupid) benoit@0: benoit@0: def getTitle(self): benoit@0: return self._type benoit@0: benoit@0: def validate(self, productids, groupids): benoit@0: if not self._type: benoit@0: raise ValidationError('A Threat must have a Type') benoit@0: if self._type not in self.TYPES: benoit@0: raise ValidationError("A Threat's Type must be one of %s" % ', '.join(self.TYPES)) benoit@0: if not self._description: benoit@0: raise ValidationError('A Threat must have a Description') benoit@0: for productid in self._productids: benoit@0: if productid not in productids: benoit@0: raise ValidationError('Unknown ProductID: %s' % productid) benoit@0: for groupid in self._groupids: benoit@0: if groupid not in groupids: benoit@0: raise ValidationError('Unknown GroupID: %s' % groupid) benoit@0: benoit@0: benoit@0: class CVRFCVSSSet(object): benoit@0: # To determine the base Score benoit@0: VALUES = {'AV': {'L':0.395, 'A':0.646, 'N':1.0}, benoit@0: 'AC': {'H':0.35, 'M':0.61 ,'L':0.71}, benoit@0: 'Au': {'M':0.45, 'S':0.56, 'N':0.704}, benoit@0: 'C': {'N':0.0, 'P':0.275, 'C':0.66}, benoit@0: 'I': {'N':0.0, 'P':0.275, 'C':0.66}, benoit@0: 'A': {'N':0.0, 'P':0.275, 'C':0.66}} benoit@17: NAME = "CVSS Score Set" benoit@0: def __init__(self, basescore): benoit@0: self._basescore = basescore benoit@0: self._temporalscore = None benoit@0: self._environmentalscore = None benoit@0: self._vector = None benoit@0: self.vector = None benoit@0: self._productids = [] benoit@0: benoit@0: def setTemporalScore(self, tempscore): benoit@0: self._temporalscore = tempscore benoit@0: benoit@0: def setEnvironmentalScore(self, envscore): benoit@0: self._environmentalscore = envscore benoit@0: benoit@0: def setVector(self, vector): benoit@0: self._vector = vector benoit@0: if vector is None: benoit@0: self.vector = vector benoit@0: return benoit@0: try: benoit@0: self.vector = {} benoit@0: for component in vector[:26].split('/'): benoit@0: name, value = component.split(':') benoit@0: self.vector[name] = self.VALUES[name][value] benoit@0: except (KeyError, ValueError): benoit@0: self.vector = None benoit@0: benoit@0: def addProductID(self, productid): benoit@0: self._productids.append(productid) benoit@0: benoit@0: def baseScore(self): benoit@0: v = self.vector # make an alias for shorter lines benoit@0: exploitability = 20 * v['AV'] * v['AC'] * v['Au'] benoit@0: impact = 10.41 * (1 - (1 - v['C']) * (1 - v['I']) * (1 - v['A'])) benoit@0: def f(i): return 0 if i == 0 else 1.176 benoit@0: return ((0.6 * impact) + (0.4 * exploitability) - 1.5) * f(impact) benoit@0: benoit@0: def validate(self, productids): benoit@0: if not self._basescore: benoit@0: raise ValidationError('A CVSS Score Set must have a Base Score') benoit@0: if self._vector and not self.vector: benoit@0: raise ValidationError('Syntax Error in CVSS Vector') benoit@22: if self.vector and (abs(self._basescore - self.baseScore()) >= 0.05): benoit@0: raise ValidationError('Inconsistency in CVSS Score Set between Vector (%f) and Base Score (%f)' % (self.baseScore(), self._basescore)) benoit@0: for productid in self._productids: benoit@0: if productid not in productids: benoit@0: raise ValidationError('Unknown ProductID: %s' % productid) benoit@0: benoit@0: benoit@0: class CVRFRemediation(object): benoit@0: TYPES = ('Workaround', 'Mitigation', 'Vendor Fix', 'None Available', benoit@0: 'Will Not Fix') benoit@17: NAME = "Remediation" benoit@0: def __init__(self, _type, description): benoit@0: self._type = _type benoit@0: self._description = description benoit@0: self._date = None benoit@0: self._entitlement = None benoit@0: self._url = None benoit@0: self._productids = [] benoit@0: self._groupids = [] benoit@0: benoit@0: def setDate(self, date): benoit@0: self._date = date benoit@0: benoit@0: def setEntitlement(self, entitlement): benoit@0: self._entitlement = entitlement benoit@0: benoit@0: def setURL(self, url): benoit@0: self._url = url benoit@0: benoit@0: def addProductID(self, productid): benoit@0: self._productids.append(productid) benoit@0: benoit@0: def addGroupID(self, groupid): benoit@0: self._groupids.append(groupid) benoit@0: benoit@0: def getTitle(self): benoit@0: return self._type benoit@0: benoit@0: def validate(self, productids, groupids): benoit@0: if not self._type: benoit@0: raise ValidationError('A Remediation must have a Type') benoit@0: if self._type not in self.TYPES: benoit@0: raise ValidationError("A Remediation's Type must be one of %s" % ', '.join(self.TYPES)) benoit@0: if not self._description: benoit@0: raise ValidationError('A Remediation must have a Description') benoit@0: for productid in self._productids: benoit@0: if productid not in productids: benoit@0: raise ValidationError('Unknown ProductID: %s' % productid) benoit@0: for groupid in self._groupids: benoit@0: if groupid not in groupids: benoit@0: raise ValidationError('Unknown GroupID: %s' % groupid) benoit@0: