Skip to content

Commit e38eb1b

Browse files
authored
Merge pull request #747 from ziadhany/calculate_cvss_vector
add support for calculating CVSS score from the CVSS vector
2 parents 0a0460b + 550ac20 commit e38eb1b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1536
-687
lines changed

pyproject.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,19 @@ addopts = [
7474
line-length = 100
7575
include = '\.pyi?$'
7676
skip_gitignore = true
77-
extend-exclude = "migrations|data|venv"
77+
# 'extend-exclude' excludes files or directories in addition to the defaults
78+
extend-exclude = '''
79+
(
80+
^/venv/.*
81+
| ^/vulnerabilities/migrations/.*
82+
| ^/vulnerabilities/tests/test_data/.*
83+
)
84+
'''
85+
7886

7987
[tool.isort]
8088
profile = "black"
8189
line_length = 100
8290
force_single_line = true
8391
skip_gitignore = true
84-
skip_glob = "*/migrations/*"
92+
skip_glob = "vulnerabilities/migrations/*"

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ install_requires =
8383
defusedxml>=0.7.1
8484
Markdown>=3.3.0
8585
dateparser>=1.1.1
86+
cvss>=2.4
8687

8788
# networking
8889
GitPython>=3.1.17

vulnerabilities/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
class VulnerabilitySeveritySerializer(serializers.ModelSerializer):
3030
class Meta:
3131
model = VulnerabilitySeverity
32-
fields = ["value", "scoring_system"]
32+
fields = ["value", "scoring_system", "scoring_elements"]
3333

3434

3535
class VulnerabilityReferenceSerializer(serializers.ModelSerializer):

vulnerabilities/importer.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,16 @@
4646

4747
@dataclasses.dataclass(order=True)
4848
class VulnerabilitySeverity:
49+
# FIXME: this should be named scoring_system, like in the model
4950
system: ScoringSystem
5051
value: str
52+
scoring_elements: str = ""
5153

5254
def to_dict(self):
5355
return {
5456
"system": self.system.identifier,
5557
"value": self.value,
58+
"scoring_elements": self.scoring_elements,
5659
}
5760

5861
@classmethod
@@ -61,7 +64,11 @@ def from_dict(cls, severity: dict):
6164
Return a VulnerabilitySeverity object from a ``severity`` mapping of
6265
VulnerabilitySeverity data.
6366
"""
64-
return cls(system=SCORING_SYSTEMS[severity["system"]], value=severity["value"])
67+
return cls(
68+
system=SCORING_SYSTEMS[severity["system"]],
69+
value=severity["value"],
70+
scoring_elements=severity.get("scoring_elements", ""),
71+
)
6572

6673

6774
@dataclasses.dataclass(order=True)
@@ -426,15 +433,13 @@ def get_data_from_xml_doc(
426433
# connected/linked to an OvalDefinition
427434
vuln_id = definition_data["vuln_id"]
428435
description = definition_data["description"]
429-
severities = (
430-
[
431-
VulnerabilitySeverity(
432-
system=severity_systems.GENERIC, value=definition_data.get("severity")
433-
)
434-
]
435-
if definition_data.get("severity")
436-
else []
437-
)
436+
437+
severities = []
438+
severity = definition_data.get("severity")
439+
if severity:
440+
severities.append(
441+
VulnerabilitySeverity(system=severity_systems.GENERIC, value=severity)
442+
)
438443
references = [
439444
Reference(url=url, severities=severities)
440445
for url in definition_data["reference_urls"]

vulnerabilities/importers/nvd.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,7 @@ def severities(self):
167167
vs = VulnerabilitySeverity(
168168
system=severity_systems.CVSSV3,
169169
value=str(cvss_v3.get("baseScore") or ""),
170-
)
171-
severities.append(vs)
172-
173-
vs = VulnerabilitySeverity(
174-
system=severity_systems.CVSSV3_VECTOR,
175-
value=str(cvss_v3.get("vectorString") or ""),
170+
scoring_elements=str(cvss_v3.get("vectorString") or ""),
176171
)
177172
severities.append(vs)
178173

@@ -182,12 +177,7 @@ def severities(self):
182177
vs = VulnerabilitySeverity(
183178
system=severity_systems.CVSSV2,
184179
value=str(cvss_v2.get("baseScore") or ""),
185-
)
186-
severities.append(vs)
187-
188-
vs = VulnerabilitySeverity(
189-
system=severity_systems.CVSSV2_VECTOR,
190-
value=str(cvss_v2.get("vectorString") or ""),
180+
scoring_elements=str(cvss_v2.get("vectorString") or ""),
191181
)
192182
severities.append(vs)
193183

vulnerabilities/importers/osv.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,10 @@ def get_severities(raw_data) -> Iterable[VulnerabilitySeverity]:
115115
"""
116116
for severity in raw_data.get("severity") or []:
117117
if severity.get("type") == "CVSS_V3":
118-
yield VulnerabilitySeverity(
119-
system=SCORING_SYSTEMS["cvssv3.1_vector"],
120-
value=severity["score"],
121-
)
118+
vector = severity["score"]
119+
system = SCORING_SYSTEMS["cvssv3.1"]
120+
score = system.compute(vector)
121+
yield VulnerabilitySeverity(system=system, value=score, scoring_elements=vector)
122122
else:
123123
logger.error(f"Unsupported severity type: {severity!r} for OSV id: {raw_data['id']!r}")
124124

vulnerabilities/importers/postgresql.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,12 @@ def to_advisories(data):
9595
parsed_link = urlparse.urlparse(vector_link_tag["href"])
9696
cvss3_vector = urlparse.parse_qs(parsed_link.query)["vector"]
9797
cvss3_base_score = vector_link_tag.text
98-
severities.extend(
99-
[
100-
VulnerabilitySeverity(
101-
system=severity_systems.CVSSV3, value=cvss3_base_score
102-
),
103-
VulnerabilitySeverity(
104-
system=severity_systems.CVSSV3_VECTOR, value=cvss3_vector
105-
),
106-
]
98+
severity = VulnerabilitySeverity(
99+
system=severity_systems.CVSSV3,
100+
value=cvss3_base_score,
101+
scoring_elements=cvss3_vector,
107102
)
103+
severities.append(severity)
108104
references.append(Reference(url=link, severities=severities))
109105

110106
advisories.append(

vulnerabilities/importers/redhat.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,23 @@
2828

2929
logger = logging.getLogger(__name__)
3030

31+
# FIXME: we should use a centralized retry
3132
requests_session = requests_with_5xx_retry(max_retries=5, backoff_factor=1)
3233

3334

34-
def fetch_list_of_cves() -> Iterable[List[Dict]]:
35+
def fetch_cves() -> Iterable[List[Dict]]:
3536
page_no = 1
3637
cve_data = None
3738
while True:
3839
current_url = f"https://access.redhat.com/hydra/rest/securitydata/cve.json?per_page=1000&page={page_no}" # nopep8
3940
try:
4041
response = requests_session.get(current_url)
4142
if response.status_code != requests.codes.ok:
42-
logger.error(f"Failed to fetch results from {current_url}")
43+
logger.error(f"Failed to fetch RedHat CVE results from {current_url}")
4344
break
4445
cve_data = response.json()
4546
except Exception as e:
46-
logger.error(f"Failed to fetch results from {current_url} {e}")
47+
logger.error(f"Failed to fetch RedHat CVE results from {current_url} {e}")
4748
break
4849
if not cve_data:
4950
break
@@ -65,8 +66,8 @@ class RedhatImporter(Importer):
6566
license_url = "https://access.redhat.com/documentation/en-us/red_hat_security_data_api/1.0/html/red_hat_security_data_api/legal-notice"
6667

6768
def advisory_data(self) -> Iterable[AdvisoryData]:
68-
for list_of_redhat_cves in fetch_list_of_cves():
69-
for redhat_cve in list_of_redhat_cves:
69+
for redhat_cves in fetch_cves():
70+
for redhat_cve in redhat_cves:
7071
yield to_advisory(redhat_cve)
7172

7273

@@ -154,20 +155,13 @@ def to_advisory(advisory_data):
154155

155156
redhat_scores = []
156157
cvssv3_score = advisory_data.get("cvss3_score")
158+
cvssv3_vector = advisory_data.get("cvss3_scoring_vector", "")
157159
if cvssv3_score:
158160
redhat_scores.append(
159161
VulnerabilitySeverity(
160162
system=severity_systems.CVSSV3,
161163
value=cvssv3_score,
162-
)
163-
)
164-
165-
cvssv3_vector = advisory_data.get("cvss3_scoring_vector")
166-
if cvssv3_vector:
167-
redhat_scores.append(
168-
VulnerabilitySeverity(
169-
system=severity_systems.CVSSV3_VECTOR,
170-
value=cvssv3_vector,
164+
scoring_elements=cvssv3_vector,
171165
)
172166
)
173167

vulnerabilities/importers/suse_scores.py

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,37 +26,26 @@ def updated_advisories(self):
2626

2727
@staticmethod
2828
def to_advisory(score_data):
29+
systems_by_version = {
30+
"2.0": severity_systems.CVSSV2,
31+
"3": severity_systems.CVSSV3,
32+
"3.1": severity_systems.CVSSV31,
33+
}
2934
advisories = []
35+
3036
for cve_id in score_data:
3137
severities = []
3238
for cvss_score in score_data[cve_id]["cvss"]:
33-
score = None
34-
vector = None
35-
if cvss_score["version"] == "2.0":
36-
score = VulnerabilitySeverity(
37-
system=severity_systems.CVSSV2, value=str(cvss_score["score"])
38-
)
39-
vector = VulnerabilitySeverity(
40-
system=severity_systems.CVSSV2_VECTOR, value=str(cvss_score["vector"])
41-
)
42-
43-
elif cvss_score["version"] == "3":
44-
score = VulnerabilitySeverity(
45-
system=severity_systems.CVSSV3, value=str(cvss_score["score"])
46-
)
47-
vector = VulnerabilitySeverity(
48-
system=severity_systems.CVSSV3_VECTOR, value=str(cvss_score["vector"])
49-
)
50-
51-
elif cvss_score["version"] == "3.1":
52-
score = VulnerabilitySeverity(
53-
system=severity_systems.CVSSV31, value=str(cvss_score["score"])
54-
)
55-
vector = VulnerabilitySeverity(
56-
system=severity_systems.CVSSV31_VECTOR, value=str(cvss_score["vector"])
57-
)
58-
59-
severities.extend([score, vector])
39+
cvss_version = cvss_score["version"]
40+
scoring_system = systems_by_version[cvss_version]
41+
base_score = str(cvss_score["score"])
42+
vector = str(cvss_score.get("vector", ""))
43+
score = VulnerabilitySeverity(
44+
system=scoring_system,
45+
value=base_score,
46+
scoring_elements=vector,
47+
)
48+
severities.append(score)
6049

6150
advisories.append(
6251
AdvisoryData(

vulnerabilities/improve_runner.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,16 @@ def process_inferences(inferences: List[Inference], advisory: Advisory, improver
102102
_vs, updated = VulnerabilitySeverity.objects.update_or_create(
103103
scoring_system=severity.system.identifier,
104104
reference=reference,
105-
defaults={"value": str(severity.value)},
105+
defaults={
106+
"value": str(severity.value),
107+
"scoring_elements": str(severity.scoring_elements),
108+
},
106109
)
107110
if updated:
108-
logger.info(f"Severity updated for reference {ref!r} to {severity.value!r}")
111+
logger.info(
112+
f"Severity updated for reference {ref!r} to value: {severity.value!r} "
113+
f"and scoring_elements: {severity.scoring_elements!r}"
114+
)
109115

110116
for affected_purl in inference.affected_purls or []:
111117
vulnerable_package = Package.objects.get_or_create_from_purl(purl=affected_purl)

0 commit comments

Comments
 (0)