Skip to content

Commit e484e33

Browse files
authored
Merge pull request #1740 from aboutcode-org/add_cvss3.1_pipeline
Add Pipeline to add missing CVSSV3.1 scores
2 parents fff12d6 + 5f2d228 commit e484e33

File tree

4 files changed

+165
-0
lines changed

4 files changed

+165
-0
lines changed

vulnerabilities/improvers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from vulnerabilities.improvers import valid_versions
1111
from vulnerabilities.improvers import vulnerability_status
1212
from vulnerabilities.pipelines import VulnerableCodePipeline
13+
from vulnerabilities.pipelines import add_cvss31_to_CVEs
1314
from vulnerabilities.pipelines import collect_commits
1415
from vulnerabilities.pipelines import compute_package_risk
1516
from vulnerabilities.pipelines import compute_package_version_rank
@@ -43,6 +44,7 @@
4344
compute_package_risk.ComputePackageRiskPipeline,
4445
compute_package_version_rank.ComputeVersionRankPipeline,
4546
collect_commits.CollectFixCommitsPipeline,
47+
add_cvss31_to_CVEs.CVEAdvisoryMappingPipeline,
4648
]
4749

4850
IMPROVERS_REGISTRY = {

vulnerabilities/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ class VulnerabilitySeverity(models.Model):
189189
blank=True, null=True, help_text="UTC Date of publication of the vulnerability severity"
190190
)
191191

192+
objects = BaseQuerySet.as_manager()
193+
192194
class Meta:
193195
ordering = ["url", "scoring_system", "value"]
194196

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Copyright (c) nexB Inc. and others. All rights reserved.
2+
# VulnerableCode is a trademark of nexB Inc.
3+
# SPDX-License-Identifier: Apache-2.0
4+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
5+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
6+
# See https://aboutcode.org for more information about nexB OSS projects.
7+
#
8+
import re
9+
10+
from aboutcode.pipeline import LoopProgress
11+
from django.db import transaction
12+
13+
from vulnerabilities import severity_systems
14+
from vulnerabilities.models import Advisory
15+
from vulnerabilities.models import VulnerabilitySeverity
16+
from vulnerabilities.pipelines import VulnerableCodePipeline
17+
18+
19+
class CVEAdvisoryMappingPipeline(VulnerableCodePipeline):
20+
"""
21+
Pipeline to map CVEs from VulnerabilitySeverity to corresponding Advisories with CVSS3.1 scores.
22+
"""
23+
24+
pipeline_id = "add_cvssv3.1_to_CVEs"
25+
26+
@classmethod
27+
def steps(cls):
28+
return (cls.process_cve_advisory_mapping,)
29+
30+
def process_cve_advisory_mapping(self):
31+
nvd_severities = (
32+
VulnerabilitySeverity.objects.filter(
33+
url__startswith="https://nvd.nist.gov/vuln/detail/CVE-", scoring_system="cvssv3"
34+
)
35+
.prefetch_related("vulnerabilities")
36+
.distinct()
37+
)
38+
39+
self.log(f"Processing {nvd_severities.count():,d} CVE severity records")
40+
41+
progress = LoopProgress(
42+
total_iterations=nvd_severities.count(),
43+
logger=self.log,
44+
progress_step=5,
45+
)
46+
47+
batch_size = 1000
48+
results = []
49+
50+
for severity in progress.iter(nvd_severities.paginated(per_page=batch_size)):
51+
print(severity.url)
52+
cve_pattern = re.compile(r"(CVE-\d{4}-\d{4,7})").search
53+
cve_match = cve_pattern(severity.url)
54+
if cve_match:
55+
cve_id = cve_match.group()
56+
else:
57+
self.log(f"Could not find CVE ID in URL: {severity.url}")
58+
continue
59+
60+
matching_advisories = Advisory.objects.filter(
61+
aliases=[cve_id],
62+
created_by="nvd_importer",
63+
)
64+
65+
for advisory in matching_advisories:
66+
for reference in advisory.references:
67+
for sev in reference.get("severities", []):
68+
if sev.get("system") == "cvssv3.1":
69+
results.append(
70+
{
71+
"cve_id": cve_id,
72+
"cvss31_score": sev.get("value"),
73+
"cvss31_vector": sev.get("scoring_elements"),
74+
"vulnerabilities": severity.vulnerabilities.all(),
75+
}
76+
)
77+
78+
if results:
79+
print(results)
80+
self._process_batch(results)
81+
82+
self.log(f"Completed processing CVE to Advisory mappings")
83+
84+
def _process_batch(self, results):
85+
"""
86+
Process a batch of results. Transactions are used to ensure data consistency.
87+
"""
88+
self.log(f"Processing batch of {len(results)} mappings")
89+
90+
with transaction.atomic():
91+
for result in results:
92+
self.log(
93+
f"CVE: {result['cve_id']}, "
94+
f"CVSS3.1: {result['cvss31_score']}, "
95+
f"Vector: {result['cvss31_vector']}"
96+
)
97+
98+
for vulnerability in result["vulnerabilities"]:
99+
vuln_severity, _ = VulnerabilitySeverity.objects.update_or_create(
100+
scoring_system=severity_systems.CVSSV31.identifier,
101+
url=f"https://nvd.nist.gov/vuln/detail/{result['cve_id']}",
102+
value=result["cvss31_score"],
103+
scoring_elements=result["cvss31_vector"],
104+
)
105+
vulnerability.severities.add(vuln_severity)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import unittest
2+
from unittest.mock import Mock
3+
from unittest.mock import patch
4+
5+
from django.test import TestCase
6+
7+
from vulnerabilities.models import Advisory
8+
from vulnerabilities.models import Alias
9+
from vulnerabilities.models import Vulnerability
10+
from vulnerabilities.models import VulnerabilitySeverity
11+
from vulnerabilities.pipelines.add_cvss31_to_CVEs import CVEAdvisoryMappingPipeline
12+
from vulnerabilities.severity_systems import CVSSV3
13+
from vulnerabilities.severity_systems import CVSSV31
14+
15+
16+
class TestCVEAdvisoryMappingPipeline(TestCase):
17+
def setUp(self):
18+
self.pipeline = CVEAdvisoryMappingPipeline()
19+
Advisory.objects.create(
20+
created_by="nvd_importer",
21+
aliases=["CVE-2024-1234"],
22+
references=[
23+
{
24+
"severities": [
25+
{
26+
"system": "cvssv3.1",
27+
"value": "7.5",
28+
"scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
29+
}
30+
],
31+
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-1234",
32+
}
33+
],
34+
date_collected="2024-09-27T19:38:00Z",
35+
)
36+
vuln = Vulnerability.objects.create(vulnerability_id="CVE-2024-1234")
37+
sev = VulnerabilitySeverity.objects.create(
38+
scoring_system=CVSSV3.identifier,
39+
url="https://nvd.nist.gov/vuln/detail/CVE-2024-1234",
40+
value="7.5",
41+
scoring_elements="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
42+
)
43+
vuln.severities.add(sev)
44+
45+
def test_process_cve_advisory_mapping_single_record(self):
46+
self.pipeline.process_cve_advisory_mapping()
47+
self.assertEqual(VulnerabilitySeverity.objects.count(), 2)
48+
# check if severity with cvssv3.1 is created
49+
sev = VulnerabilitySeverity.objects.get(scoring_system=CVSSV31.identifier)
50+
self.assertEqual(sev.url, "https://nvd.nist.gov/vuln/detail/CVE-2024-1234")
51+
self.assertEqual(sev.value, "7.5")
52+
self.assertEqual(sev.scoring_elements, "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N")
53+
# check if severity is added to existing vulnerability
54+
vuln = Vulnerability.objects.get(vulnerability_id="CVE-2024-1234")
55+
self.assertEqual(vuln.severities.count(), 2)
56+
self.assertIn(sev, vuln.severities.all())

0 commit comments

Comments
 (0)