11import json
22import logging
3+ import re
34from datetime import datetime
45from pathlib import Path
56from typing import Iterable
89
910import dateparser
1011from packageurl import PackageURL
12+ from univers .version_constraint import VersionConstraint
13+ from univers .version_range import RANGE_CLASS_BY_SCHEMES
14+ from univers .version_range import VersionRange
15+ from univers .versions import AlpineLinuxVersion
16+ from univers .versions import ArchLinuxVersion
17+ from univers .versions import ComposerVersion
18+ from univers .versions import DebianVersion
19+ from univers .versions import GenericVersion
20+ from univers .versions import GentooVersion
21+ from univers .versions import GolangVersion
1122from univers .versions import InvalidVersion
23+ from univers .versions import LegacyOpensslVersion
24+ from univers .versions import MavenVersion
25+ from univers .versions import NginxVersion
26+ from univers .versions import NugetVersion
27+ from univers .versions import OpensslVersion
28+ from univers .versions import PypiVersion
29+ from univers .versions import RpmVersion
1230from univers .versions import SemverVersion
1331from univers .versions import Version
1432
2341from vulnerabilities .utils import get_advisory_url
2442from vulnerabilities .utils import get_cwe_id
2543
44+ logger = logging .getLogger (__name__ )
45+
46+ VULNRICH_VERSION_CLASS_SCHEMES = {
47+ "semver" : SemverVersion ,
48+ "python" : PypiVersion ,
49+ "custom" : GenericVersion ,
50+ "rpm" : RpmVersion ,
51+ "maven" : MavenVersion ,
52+ }
53+
2654
2755class VulnrichImporter (Importer ):
2856 spdx_license_expression = "CC0-1.0"
@@ -44,7 +72,7 @@ def advisory_data(self) -> Iterable[AdvisoryData]:
4472 advisory_url = get_advisory_url (
4573 file = file_path ,
4674 base_path = base_path ,
47- url = "https://github.com/rubysec/ruby-advisory-db /blob/master /" ,
75+ url = "https://github.com/cisagov/vulnrichment /blob/develop /" ,
4876 )
4977 yield parse_cve_advisory (raw_data , advisory_url )
5078 finally :
@@ -53,12 +81,12 @@ def advisory_data(self) -> Iterable[AdvisoryData]:
5381
5482
5583def parse_cve_advisory (raw_data , advisory_url ):
56- """"""
57-
84+ """ """
5885 # Extract CVE Metadata
5986 cve_metadata = raw_data .get ("cveMetadata" , {})
6087 cve_id = cve_metadata .get ("cveId" )
6188 state = cve_metadata .get ("state" )
89+
6290 date_published = cve_metadata .get ("datePublished" )
6391 date_published = dateparser .parse (date_published )
6492
@@ -68,25 +96,75 @@ def parse_cve_advisory(raw_data, advisory_url):
6896 adp_data = containers .get ("adp" , {})
6997
7098 # Extract affected products
71- # affected_products = cna_data.get("affected", [])
72- # products = []
73- # for product in affected_products:
74- # product_info = {
75- # "default_status": product.get("defaultStatus"),
76- # "platforms": product.get("platforms", []),
77- # "product": product.get("product"),
78- # "vendor": product.get("vendor"),
79- # "versions": product.get("versions", [])
80- # }
81- # products.append(product_info)
99+ affected_packages = []
100+ for affected_product in cna_data .get ("affected" , []):
101+ if type (affected_product ) != dict :
102+ continue
103+ cpes = affected_product .get ("cpes" ) # TODO Add references cpes
104+
105+ vendor = affected_product .get ("vendor" ) or ""
106+ collection_url = affected_product .get ("collectionURL" ) or ""
107+ product = affected_product .get ("product" ) or ""
108+ package_name = affected_product .get ("packageName" ) or ""
109+
110+ platforms = affected_product .get ("platforms" , [])
111+ default_status = affected_product .get ("defaultStatus" )
112+
113+ affected_packages = []
114+ # purl (vendor, collection_url, product, package_name, platforms)
115+ purl = PackageURL (
116+ type = vendor ,
117+ name = product ,
118+ namespace = package_name ,
119+ )
120+
121+ versions = affected_product .get ("versions" , [])
122+ for version_data in versions :
123+ # version ≤ V ≤ (lessThanOrEqual/lessThan)
124+ # right_version ≤ V ≤ left_version
125+ version_constraints = []
126+ r_version = version_data .get ("version" )
127+ version_type = version_data .get ("versionType" )
128+ version_class = VULNRICH_VERSION_CLASS_SCHEMES .get (version_type )
129+ if not version_class :
130+ logger .error (f"Invalid version_class type: { version_type } " )
131+ continue
132+
133+ l_version , l_comparator = None , ""
134+ if "lessThan" in version_data :
135+ l_version = version_data .get ("lessThan" )
136+ l_comparator = "<"
137+ elif "lessThanOrEqual" in version_data :
138+ l_version = version_data .get ("lessThanOrEqual" )
139+ l_comparator = "<="
140+ try :
141+ if l_version and l_comparator :
142+ version_constraints .append (
143+ VersionConstraint (comparator = l_comparator , version = version_class (l_version ))
144+ )
145+ if r_version :
146+ version_constraints .append (
147+ VersionConstraint (comparator = ">" , version = version_class (r_version ))
148+ )
149+ except InvalidVersion :
150+ logger .error (f"InvalidVersion: { l_version } -{ r_version } " )
151+ continue
152+
153+ affected_packages .append (
154+ AffectedPackage (
155+ purl ,
156+ affected_version_range = VersionRange (constraints = version_constraints ),
157+ )
158+ )
159+ status = version_data .get ("status" )
82160
83161 # Extract descriptions
84- description = ""
162+ summary = ""
85163 description_list = cna_data .get ("descriptions" , [])
86164 for description_dict in description_list :
87- if description_dict .get ("lang" ) != "en" :
165+ if not description_dict .get ("lang" ) in [ "en" , "en-US" ] :
88166 continue
89- description = description_dict .get ("value" )
167+ summary = description_dict .get ("value" )
90168
91169 # Extract metrics
92170 severities = []
@@ -98,7 +176,7 @@ def parse_cve_advisory(raw_data, advisory_url):
98176 "cvssV2_0" : SCORING_SYSTEMS ["cvssv2" ],
99177 "other" : {
100178 "ssvc" : SCORING_SYSTEMS ["ssvc" ],
101- },
179+ }, # ignore kev
102180 }
103181
104182 for metric in metrics :
@@ -127,35 +205,34 @@ def parse_cve_advisory(raw_data, advisory_url):
127205 severities .append (severity )
128206
129207 # Extract references
208+ # TODO ADD reference type
130209 references = [
131210 Reference (url = ref .get ("url" ), severities = severities )
132211 for ref in cna_data .get ("references" , [])
133212 ]
134213
135- # Extract problem types
136214 weaknesses = []
137- # problem_types = cna_data.get("problemTypes", [])
138- # for problem in problem_types:
139- # descriptions = problem.get("descriptions", [])
140- # for description in descriptions:
141- # weaknesses.append(
142- # description.get("cweId")
143- # "description": description.get("description"),
144- # "lang": description.get("lang"),
145- # "type": description.get("type")
146- # )
147- #
148- # # cwe_id = description.get("cweId")
149- # # cwe_id = get_cwe_id(cwe_id)
150- # # weaknesses.append(cwe_id)
215+ for problem_type in cna_data .get ("problemTypes" , []):
216+ descriptions = problem_type .get ("descriptions" , [])
217+ for description in descriptions :
218+ cwe_id = description .get ("cweId" )
219+ if cwe_id :
220+ weaknesses .append (get_cwe_id (cwe_id ))
221+
222+ description_text = description .get ("description" )
223+ if description_text :
224+ pattern = r"CWE-(\d{3})"
225+ match = re .search (pattern , description_text )
226+ if match :
227+ weaknesses .append (match .group (1 ))
151228
152229 return AdvisoryData (
153230 aliases = [cve_id ],
154- summary = description ,
155- # affected_packages=affected_products ,
231+ summary = summary ,
232+ affected_packages = affected_packages ,
156233 references = references ,
157- date_published = date_published ,
158- # weaknesses=weaknesses,
234+ # date_published=dateparser.parse(self.cve_item.get("publishedDate")) ,
235+ weaknesses = weaknesses ,
159236 url = advisory_url ,
160237 )
161238
@@ -218,6 +295,7 @@ def ssvc_calculator(ssvc_data):
218295
219296 # "Decision": {"D": {"Track": "T", "Track*": "R", "Attend": "A", "Act": "C"}},
220297 decision_values = {"Track" : "T" , "Track*" : "R" , "Attend" : "A" , "Act" : "C" }
298+
221299 decision_lookup = {
222300 ("none" , "no" , "partial" , "low" ): "Track" ,
223301 ("none" , "no" , "partial" , "medium" ): "Track" ,
@@ -262,8 +340,8 @@ def ssvc_calculator(ssvc_data):
262340 if decision :
263341 ssvc_vector += f"D:{ decision_values .get (decision )} /"
264342
265- timestamp_formatted = datetime . strptime ( timestamp , "%Y-%m-%dT%H:%M:%S.%fZ" ). strftime (
266- "%Y-%m-%dT%H:%M:%SZ"
267- )
268- ssvc_vector += f"{ timestamp_formatted } /"
343+ if timestamp :
344+ timestamp_formatted = dateparser . parse ( timestamp ). strftime ( "%Y-%m-%dT%H:%M:%SZ" )
345+
346+ ssvc_vector += f"{ timestamp_formatted } /"
269347 return ssvc_vector , decision
0 commit comments