Skip to content

Commit 95d66f3

Browse files
committed
Add SSVC calculator.
Add Support for SSVC. Add vulnrichment importer. Signed-off-by: ziadhany <[email protected]>
1 parent 94b5878 commit 95d66f3

10 files changed

+1311
-0
lines changed

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from vulnerabilities.importers import suse_scores
3838
from vulnerabilities.importers import ubuntu
3939
from vulnerabilities.importers import ubuntu_usn
40+
from vulnerabilities.importers import vulnrichment
4041
from vulnerabilities.importers import xen
4142

4243
IMPORTERS_REGISTRY = [
@@ -71,6 +72,7 @@
7172
oss_fuzz.OSSFuzzImporter,
7273
ruby.RubyImporter,
7374
github_osv.GithubOSVImporter,
75+
vulnrichment.VulnrichImporter,
7476
]
7577

7678
IMPORTERS_REGISTRY = {x.qualified_name: x for x in IMPORTERS_REGISTRY}
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
import json
2+
import logging
3+
from datetime import datetime
4+
from pathlib import Path
5+
from typing import Iterable
6+
from typing import List
7+
from typing import Optional
8+
9+
import dateparser
10+
from packageurl import PackageURL
11+
from univers.versions import InvalidVersion
12+
from univers.versions import SemverVersion
13+
from univers.versions import Version
14+
15+
from vulnerabilities.importer import AdvisoryData
16+
from vulnerabilities.importer import AffectedPackage
17+
from vulnerabilities.importer import Importer
18+
from vulnerabilities.importer import Reference
19+
from vulnerabilities.importer import VulnerabilitySeverity
20+
from vulnerabilities.severity_systems import SCORING_SYSTEMS
21+
from vulnerabilities.utils import build_description
22+
from vulnerabilities.utils import dedupe
23+
from vulnerabilities.utils import get_advisory_url
24+
from vulnerabilities.utils import get_cwe_id
25+
26+
27+
class VulnrichImporter(Importer):
28+
spdx_license_expression = "CC0-1.0"
29+
license_url = "https://github.com/cisagov/vulnrichment/blob/develop/LICENSE"
30+
repo_url = "git+https://github.com/cisagov/vulnrichment.git"
31+
importer_name = "Vulnrichment"
32+
33+
def advisory_data(self) -> Iterable[AdvisoryData]:
34+
try:
35+
vcs_response = self.clone(repo_url=self.repo_url)
36+
base_path = Path(vcs_response.dest_dir)
37+
for file_path in base_path.glob(f"**/**/*.json"):
38+
if not file_path.name.startswith("CVE-"):
39+
continue
40+
41+
with open(file_path) as f:
42+
raw_data = json.load(f)
43+
44+
advisory_url = get_advisory_url(
45+
file=file_path,
46+
base_path=base_path,
47+
url="https://github.com/rubysec/ruby-advisory-db/blob/master/",
48+
)
49+
yield parse_cve_advisory(raw_data, advisory_url)
50+
finally:
51+
if self.vcs_response:
52+
self.vcs_response.delete()
53+
54+
55+
def parse_cve_advisory(raw_data, advisory_url):
56+
""""""
57+
58+
# Extract CVE Metadata
59+
cve_metadata = raw_data.get("cveMetadata", {})
60+
cve_id = cve_metadata.get("cveId")
61+
state = cve_metadata.get("state")
62+
date_published = cve_metadata.get("datePublished")
63+
date_published = dateparser.parse(date_published)
64+
65+
# Extract containers
66+
containers = raw_data.get("containers", {})
67+
cna_data = containers.get("cna", {})
68+
adp_data = containers.get("adp", {})
69+
70+
# 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)
82+
83+
# Extract descriptions
84+
description = ""
85+
description_list = cna_data.get("descriptions", [])
86+
for description_dict in description_list:
87+
if description_dict.get("lang") != "en":
88+
continue
89+
description = description_dict.get("value")
90+
91+
# Extract metrics
92+
severities = []
93+
metrics = cna_data.get("metrics", []) + [data.get("metrics", [])[0] for data in adp_data]
94+
vulnrichment_scoring_system = {
95+
"cvssV4_0": SCORING_SYSTEMS["cvssv4"],
96+
"cvssV3_1": SCORING_SYSTEMS["cvssv3.1"],
97+
"cvssV3_0": SCORING_SYSTEMS["cvssv3"],
98+
"cvssV2_0": SCORING_SYSTEMS["cvssv2"],
99+
"other": {
100+
"ssvc": SCORING_SYSTEMS["ssvc"],
101+
},
102+
}
103+
104+
for metric in metrics:
105+
for metric_type, metric_value in metric.items():
106+
if metric_type not in vulnrichment_scoring_system:
107+
continue
108+
109+
if metric_type == "other":
110+
other_types = metric_value.get("type")
111+
if other_types == "ssvc":
112+
content = metric_value.get("content", {})
113+
vector_string, decision = ssvc_calculator(content)
114+
scoring_system = vulnrichment_scoring_system[metric_type][other_types]
115+
severity = VulnerabilitySeverity(
116+
system=scoring_system, scoring_elements=vector_string, value=decision
117+
)
118+
severities.append(severity)
119+
# ignore kev
120+
else:
121+
vector_string = metric_value.get("vectorString")
122+
base_score = metric_value.get("baseScore")
123+
scoring_system = vulnrichment_scoring_system[metric_type]
124+
severity = VulnerabilitySeverity(
125+
system=scoring_system, value=base_score, scoring_elements=vector_string
126+
)
127+
severities.append(severity)
128+
129+
# Extract references
130+
references = [
131+
Reference(url=ref.get("url"), severities=severities)
132+
for ref in cna_data.get("references", [])
133+
]
134+
135+
# Extract problem types
136+
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)
151+
152+
return AdvisoryData(
153+
aliases=[cve_id],
154+
summary=description,
155+
# affected_packages=affected_products,
156+
references=references,
157+
date_published=date_published,
158+
# weaknesses=weaknesses,
159+
url=advisory_url,
160+
)
161+
162+
163+
def ssvc_calculator(ssvc_data):
164+
"""
165+
Return the ssvc vector and the decision value
166+
"""
167+
options = ssvc_data.get("options", [])
168+
timestamp = ssvc_data.get("timestamp")
169+
170+
# Extract the options into a dictionary
171+
options_dict = {list(option.keys())[0]: list(option.values())[0].lower() for option in options}
172+
173+
# Determining Mission and Well-Being Impact Value
174+
mission_well_being_table = {
175+
# (Mission Prevalence, Public Well-being Impact) : "Mission & Well-being"
176+
("minimal", "minimal"): "low",
177+
("minimal", "material"): "medium",
178+
("minimal", "irreversible"): "high",
179+
("support", "minimal"): "medium",
180+
("support", "material"): "medium",
181+
("support", "material"): "high",
182+
("essential", "minimal"): "high",
183+
("essential", "material"): "high",
184+
("essential", "irreversible"): "high",
185+
}
186+
if "Mission Prevalence" not in options_dict:
187+
options_dict["Mission Prevalence"] = "minimal"
188+
189+
if "Public Well-being Impact" not in options_dict:
190+
options_dict["Public Well-being Impact"] = "material"
191+
192+
options_dict["Mission & Well-being"] = mission_well_being_table[
193+
(options_dict["Mission Prevalence"], options_dict["Public Well-being Impact"])
194+
]
195+
196+
decision_key = (
197+
options_dict.get("Exploitation"),
198+
options_dict.get("Automatable"),
199+
options_dict.get("Technical Impact"),
200+
options_dict.get("Mission & Well-being"),
201+
)
202+
203+
decision_points = {
204+
"Exploitation": {"E": {"none": "N", "poc": "P", "active": "A"}},
205+
"Automatable": {"A": {"no": "N", "yes": "Y"}},
206+
"Technical Impact": {"T": {"partial": "P", "total": "T"}},
207+
"Public Well-being Impact": {"B": {"minimal": "M", "material": "A", "irreversible": "I"}},
208+
"Mission Prevalence": {"P": {"minimal": "M", "support": "S", "essential": "E"}},
209+
"Mission & Well-being": {"M": {"low": "L", "medium": "M", "high": "H"}},
210+
}
211+
212+
# Create the SSVC vector
213+
ssvc_vector = "SSVCv2/"
214+
for key, value_map in options_dict.items():
215+
options_key = decision_points.get(key)
216+
for lhs, rhs_map in options_key.items():
217+
ssvc_vector += f"{lhs}:{rhs_map.get(value_map)}/"
218+
219+
# "Decision": {"D": {"Track": "T", "Track*": "R", "Attend": "A", "Act": "C"}},
220+
decision_values = {"Track": "T", "Track*": "R", "Attend": "A", "Act": "C"}
221+
decision_lookup = {
222+
("none", "no", "partial", "low"): "Track",
223+
("none", "no", "partial", "medium"): "Track",
224+
("none", "no", "partial", "high"): "Track",
225+
("none", "no", "total", "low"): "Track",
226+
("none", "no", "total", "medium"): "Track",
227+
("none", "no", "total", "high"): "Track*",
228+
("none", "yes", "partial", "low"): "Track",
229+
("none", "yes", "partial", "medium"): "Track",
230+
("none", "yes", "partial", "high"): "Attend",
231+
("none", "yes", "total", "low"): "Track",
232+
("none", "yes", "total", "medium"): "Track",
233+
("none", "yes", "total", "high"): "Attend",
234+
("poc", "no", "partial", "low"): "Track",
235+
("poc", "no", "partial", "medium"): "Track",
236+
("poc", "no", "partial", "high"): "Track*",
237+
("poc", "no", "total", "low"): "Track",
238+
("poc", "no", "total", "medium"): "Track*",
239+
("poc", "no", "total", "high"): "Attend",
240+
("poc", "yes", "partial", "low"): "Track",
241+
("poc", "yes", "partial", "medium"): "Track",
242+
("poc", "yes", "partial", "high"): "Attend",
243+
("poc", "yes", "total", "low"): "Track",
244+
("poc", "yes", "total", "medium"): "Track*",
245+
("poc", "yes", "total", "high"): "Attend",
246+
("active", "no", "partial", "low"): "Track",
247+
("active", "no", "partial", "medium"): "Track",
248+
("active", "no", "partial", "high"): "Attend",
249+
("active", "no", "total", "low"): "Track",
250+
("active", "no", "total", "medium"): "Attend",
251+
("active", "no", "total", "high"): "Act",
252+
("active", "yes", "partial", "low"): "Attend",
253+
("active", "yes", "partial", "medium"): "Attend",
254+
("active", "yes", "partial", "high"): "Act",
255+
("active", "yes", "total", "low"): "Attend",
256+
("active", "yes", "total", "medium"): "Act",
257+
("active", "yes", "total", "high"): "Act",
258+
}
259+
260+
decision = decision_lookup.get(decision_key, "")
261+
262+
if decision:
263+
ssvc_vector += f"D:{decision_values.get(decision)}/"
264+
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}/"
269+
return ssvc_vector, decision

vulnerabilities/severity_systems.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#
99

1010
import dataclasses
11+
from datetime import datetime
1112

1213
from cvss import CVSS2
1314
from cvss import CVSS3
@@ -181,6 +182,12 @@ def get(self, scoring_elements: str) -> dict:
181182
"Low",
182183
]
183184

185+
SSVC = ScoringSystem(
186+
identifier="ssvc",
187+
name="Stakeholder-Specific Vulnerability Categorization",
188+
url="https://www.cisa.gov/stakeholder-specific-vulnerability-categorization-ssvc",
189+
)
190+
184191
SCORING_SYSTEMS = {
185192
system.identifier: system
186193
for system in (
@@ -195,5 +202,6 @@ def get(self, scoring_elements: str) -> dict:
195202
GENERIC,
196203
APACHE_HTTPD,
197204
APACHE_TOMCAT,
205+
SSVC,
198206
)
199207
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"aliases": [
3+
"CVE-2024-3018"
4+
],
5+
"summary": "",
6+
"affected_packages": [],
7+
"references": [
8+
{
9+
"reference_id": "",
10+
"url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/342049e5-834e-4867-8174-01ca7bb0caa2?source=cve",
11+
"severities": [
12+
{
13+
"system": "cvssv3.1",
14+
"value": 8.8,
15+
"scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"
16+
},
17+
{
18+
"system": "ssvc",
19+
"value": "Track",
20+
"scoring_elements": "SSVCv2/E:N/A:N/T:P/P:M/B:A/M:M/D:T/2024-04-01T17:33:59Z/"
21+
}
22+
]
23+
},
24+
{
25+
"reference_id": "",
26+
"url": "https://plugins.trac.wordpress.org/changeset/3060417/essential-addons-for-elementor-lite",
27+
"severities": [
28+
{
29+
"system": "cvssv3.1",
30+
"value": 8.8,
31+
"scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"
32+
},
33+
{
34+
"system": "ssvc",
35+
"value": "Track",
36+
"scoring_elements": "SSVCv2/E:N/A:N/T:P/P:M/B:A/M:M/D:T/2024-04-01T17:33:59Z/"
37+
}
38+
]
39+
}
40+
],
41+
"date_published": "2024-03-30T11:17:25.675000+00:00",
42+
"weaknesses": [],
43+
"url": "http://test.com"
44+
}

0 commit comments

Comments
 (0)