# HG changeset patch # User Benoît Allard # Date 1419867214 -3600 # Node ID b87f2a6e613a0399b3ca2431583fe59d9d193036 # Parent 9ed24f48df016a415524db9b192b679df9a5dea3 Add CVE parsing (from OpenVAS GSA) diff -r 9ed24f48df01 -r b87f2a6e613a CHANGES --- a/CHANGES Mon Dec 29 15:00:59 2014 +0100 +++ b/CHANGES Mon Dec 29 16:33:34 2014 +0100 @@ -4,6 +4,7 @@ Main changes since FarolLuz 1.0: -------------------------------- * Add parsing for CPE. +* Add parsing for CVEs format from the OpenVAS Greenbone Security Manager. FarolLuz 1.0 (2014-11-05) diff -r 9ed24f48df01 -r b87f2a6e613a farolluz/document.py --- a/farolluz/document.py Mon Dec 29 15:00:59 2014 +0100 +++ b/farolluz/document.py Mon Dec 29 16:33:34 2014 +0100 @@ -82,6 +82,7 @@ class CVRFTracking(object): STATUSES = ('Draft', 'Interim', 'Final') def __init__(self, _id, status, version, initial, current): + """ version must be a tuple of (max four) ints """ self._identification = _id self._status = status self._version = version @@ -148,6 +149,7 @@ class CVRFRevision(object): def __init__(self, number, date, description): + """ number is a tuple of (max four) ints """ self._number = number self._date = date self._description = description diff -r 9ed24f48df01 -r b87f2a6e613a farolluz/parsers/cve.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/farolluz/parsers/cve.py Mon Dec 29 16:33:34 2014 +0100 @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +# Description: +# Methods for parsing CVE XML documents +# +# Authors: +# Benoît Allard +# +# Copyright: +# Copyright (C) 2014 Greenbone Networks GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + +"""\ +Methods for parsing of CVE XML Documents + +Ref: http://scap.nist.gov/schema/vulnerability/0.4 +""" + +from __future__ import absolute_import + +import xml.etree.ElementTree as ET + +from .xml import parseDate + +from ..common import CVRFNote, CVRFReference +from ..document import CVRF, CVRFPublisher, CVRFTracking, CVRFTrackingID, CVRFRevision +from ..producttree import CVRFFullProductName +from ..utils import utcnow +from ..vulnerability import CVRFVulnerability, CVRFCVSSSet, CVRFCWE, CVRFProductStatus + +NAMESPACES = { + 'cve': "http://scap.nist.gov/schema/feed/vulnerability/2.0", + 'vuln': "http://scap.nist.gov/schema/vulnerability/0.4", + 'cvss': "http://scap.nist.gov/schema/cvss-v2/0.2", + 'xml': "http://www.w3.org/XML/1998/namespace", +} + + +def UN(ns, name): + """ returns a Universal Name """ + return "{%s}%s" % (NAMESPACES[ns], name) + +def parseCVSS(xmlElem): + """ Make a vector out of a list of elements """ + def get(name): + return xmlElem.findtext('/'.join([UN('cvss', 'base_metrics'), UN('cvss', name)])) + + cvss_set = CVRFCVSSSet(float(get('score'))) + vector = [ + 'AV:%s' % {'LOCAL': 'L', + 'ADJACENT_NETWORK': 'A', + 'NETWORK': 'N'}[get('access-vector')], + 'AC:%s' % {'HIGH': 'H', + 'MEDIUM': 'M', + 'LOW': 'L'}[get('access-complexity')], + 'Au:%s' % {'MULTIPLE': 'M', + 'SINGLE': 'S', + 'NONE': 'N'}[get('authentication')], + 'C:%s' % {'NONE': 'N', + 'PARTIAL': 'P', + 'COMPLETE': 'C'}[get('confidentiality-impact')], + 'I:%s' % {'NONE': 'N', + 'PARTIAL': 'P', + 'COMPLETE': 'C'}[get('integrity-impact')], + 'A:%s' % {'NONE': 'N', + 'PARTIAL': 'P', + 'COMPLETE': 'C'}[get('availability-impact')], + ] + cvss_set.setVector('/'.join(vector)) + return cvss_set + +def parseXML(data): + """ returns am ET.Element from the input stuff. + input can be: + - a string + - a file handle + - an ET.Element instance + """ + if isinstance(data, ET.Element): + return data + # To allow passing file handles + if hasattr(data, 'read'): + data = data.read() + # Parse it. + return ET.fromstring(data) + +def parse_CVE_from_GSA(data): + xml = parseXML(data) + return parse(xml.find('/'.join(['get_info', 'get_info_response', 'info', 'cve', 'raw_data', UN('cve', 'entry')]))) + +def parse(xml): + xml = parseXML(xml) + + # Create an extra-minimal document + doc = CVRF(xml.findtext(UN('vuln', 'cve-id')), + 'Vulnerability Description') + pub = CVRFPublisher("Other") + doc.setPublisher(pub) + now = utcnow() + tracking = CVRFTracking( + CVRFTrackingID('000000'), + "Draft", + (0,), + now, now + ) + doc.setTracking(tracking) + tracking.addRevision(CVRFRevision((0,), now, 'Document created')) + + # Add the CVE to that document + return addToDoc(doc, xml) + +def addToDoc(doc, xml): + """ Adds the CVE as vulnerability in the document """ + xml = parseXML(xml) + + vulnid = xml.attrib['id'] + + # Get a new ordinal for our new Vulnerability + if len(doc._vulnerabilities) == 0: + ordinal = 1 + else: + ordinal = doc._vulnerabilities[-1]._ordinal + 1 + + # Create a Vulnerability + vuln = CVRFVulnerability(ordinal) + doc.addVulnerability(vuln) + + vulnerable_products = [] + # Set the vulnerable products in productTree + for i, cpe in enumerate(xml.findall( + '/'.join([UN('vuln', 'vulnerable-software-list'), + UN('vuln', 'product')]))): + if doc._producttree is None: + doc.createProductTree() + try: + prod = doc._producttree.getProductForCPE(cpe.text) + except KeyError: + prod = CVRFFullProductName('%s-P%d' % (vulnid, i), cpe.text, doc._producttree, cpe.text) + doc._producttree.addProduct(prod) + vulnerable_products.append(prod) + + if vulnerable_products: + status = CVRFProductStatus('Known Affected') + for product in vulnerable_products: + status.addProductID(product._productid) + vuln.addProductStatus(status) + + # Add the CVE-id + vuln.setCVE(xml.findtext(UN('vuln', 'cve-id'))) + + # The release date + vuln.setReleaseDate(parseDate(xml.findtext(UN('vuln', 'published-datetime')))) + + # Add the CVSS + xmlcvss = xml.find(UN('vuln', 'cvss')) + if xmlcvss is not None: + vuln.addCVSSSet(parseCVSS(xmlcvss)) + + # Add the CWE id + xmlcwe = xml.find(UN('vuln', 'cwe')) + if xmlcwe is not None: + # XXX: Get a Description for the CWE ! + vuln.addCWE(CVRFCWE(xmlcwe.attrib['id'], xmlcwe.attrib['id'])) + + # Add references + for xmlref in xml.findall(UN('vuln', 'references')): + vuln.addReference(CVRFReference(xmlref.find(UN('vuln','reference')).attrib['href'], + xmlref.findtext(UN('vuln', 'reference')))) + + xmlsummary = xml.findtext(UN('vuln', 'summary')) + if xmlsummary is not None: + vuln.addNote(CVRFNote( + 'Summary', + 1, + xmlsummary + )) + + return doc diff -r 9ed24f48df01 -r b87f2a6e613a farolluz/producttree.py --- a/farolluz/producttree.py Mon Dec 29 15:00:59 2014 +0100 +++ b/farolluz/producttree.py Mon Dec 29 16:33:34 2014 +0100 @@ -51,6 +51,12 @@ return product raise KeyError(productid) + def getProductForCPE(self, cpe): + for product in self._products: + if product._cpe == cpe: + return product + raise KeyError(cpe) + def getGroupForID(self, groupid): for group in self._groups: if group._groupid == groupid: diff -r 9ed24f48df01 -r b87f2a6e613a tests/testParseCVE.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/testParseCVE.py Mon Dec 29 16:33:34 2014 +0100 @@ -0,0 +1,81 @@ +import utils + +from farolluz.parsers.cve import parse + +FULL_CVE = """\ + + + + + + + + +cpe:/a:jdm_lifestyle_project:jdm_lifestyle:6.4::~~~android~~ + + +CVE-2014-7088 +2014-10-18T21:55:17.027-04:00 +2014-11-14T09:07:51.650-05:00 + + +5.4 +ADJACENT_NETWORK +MEDIUM +NONE +PARTIAL +PARTIAL +PARTIAL +http://nvd.nist.gov +2014-11-14T09:07:51.290-05:00 + + + + +CERT-VN +VU#582497 + + +MISC + +https://docs.google.com/spreadsheets/d/1t5GXwjw82SyunALVJb2w0zi3FoLRIkfGPc7AMjRF0r4/edit?usp=sharing + + + +The JDM Lifestyle (aka com.hondatech) application 6.4 for Android does not verify X.509 certificates from SSL servers, which allows man-in-the-middle attackers to spoof servers and obtain sensitive information via a crafted certificate. + +""" + +CVE_NO_CVSS = """\ + +CVE-2014-9388 +2014-12-17T14:59:08.587-05:00 +2014-12-17T14:59:09.620-05:00 + +CONFIRM +https://www.mantisbt.org/bugs/view.php?id=17878 + + +CONFIRM + +https://www.mantisbt.org/bugs/changelog_page.php?version_id=191 + + + +MLIST +[oss-security] 20141207 MantisBT 1.2.18 Released + + +bug_report.php in MantisBT before 1.2.18 allows remote attackers to assign arbitrary issues via the handler_id parameter. + +""" + +class testCVEParsing(utils.TestCase): + + def test_Full(self): + self.doc = parse(FULL_CVE) + self._validate() + + def test_no_CVSS(self): + self.doc = parse(CVE_NO_CVSS) + self._validate()