Skip to content

Commit 65d7a58

Browse files
authored
Merge pull request #2189 from ziadhany/fix-enhance-exploit
Fix null constraint violations in multiple v1 exploit pipelines
2 parents 963058f + 11082a7 commit 65d7a58

File tree

7 files changed

+101
-48
lines changed

7 files changed

+101
-48
lines changed

vulnerabilities/pipelines/enhance_with_exploitdb.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,19 +78,16 @@ def add_exploit(self):
7878

7979

8080
def add_vulnerability_exploit(row, logger):
81-
vulnerabilities = set()
82-
8381
aliases = row["codes"].split(";") if row["codes"] else []
8482

8583
if not aliases:
8684
return 0
8785

88-
for raw_alias in aliases:
89-
try:
90-
if alias := Alias.objects.get(alias=raw_alias):
91-
vulnerabilities.add(alias.vulnerability)
92-
except Alias.DoesNotExist:
93-
continue
86+
vulnerabilities = (
87+
Alias.objects.filter(alias__in=aliases, vulnerability__isnull=False)
88+
.values_list("vulnerability_id", flat=True)
89+
.distinct()
90+
)
9491

9592
if not vulnerabilities:
9693
logger(f"No vulnerability found for aliases {aliases}")
@@ -104,7 +101,7 @@ def add_vulnerability_exploit(row, logger):
104101
add_exploit_references(row["codes"], row["source_url"], row["file"], vulnerability, logger)
105102
try:
106103
Exploit.objects.update_or_create(
107-
vulnerability=vulnerability,
104+
vulnerability_id=vulnerability,
108105
data_source="Exploit-DB",
109106
defaults={
110107
"date_added": date_added,
@@ -125,7 +122,7 @@ def add_vulnerability_exploit(row, logger):
125122
return 1
126123

127124

128-
def add_exploit_references(ref_id, direct_url, path, vul, logger):
125+
def add_exploit_references(ref_id, direct_url, path, vul_id, logger):
129126
url_map = {
130127
"file_url": f"https://gitlab.com/exploit-database/exploitdb/-/blob/main/{path}",
131128
"direct_url": direct_url,
@@ -144,7 +141,7 @@ def add_exploit_references(ref_id, direct_url, path, vul, logger):
144141

145142
if created:
146143
VulnerabilityRelatedReference.objects.get_or_create(
147-
vulnerability=vul,
144+
vulnerability_id=vul_id,
148145
reference=ref,
149146
)
150147

vulnerabilities/pipelines/enhance_with_kev.py

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -71,26 +71,29 @@ def add_vulnerability_exploit(kev_vul, logger):
7171
if not cve_id:
7272
return 0
7373

74-
vulnerability = None
75-
try:
76-
if alias := Alias.objects.get(alias=cve_id):
77-
vulnerability = alias.vulnerability
78-
except Alias.DoesNotExist:
74+
vulnerabilities = (
75+
Alias.objects.filter(alias=cve_id, vulnerability__isnull=False)
76+
.values_list("vulnerability", flat=True)
77+
.distinct()
78+
)
79+
80+
if not vulnerabilities:
7981
logger(f"No vulnerability found for aliases {cve_id}")
8082
return 0
8183

82-
Exploit.objects.update_or_create(
83-
vulnerability=vulnerability,
84-
data_source="KEV",
85-
defaults={
86-
"description": kev_vul["shortDescription"],
87-
"date_added": kev_vul["dateAdded"],
88-
"required_action": kev_vul["requiredAction"],
89-
"due_date": kev_vul["dueDate"],
90-
"notes": kev_vul["notes"],
91-
"known_ransomware_campaign_use": True
92-
if kev_vul["knownRansomwareCampaignUse"] == "Known"
93-
else False,
94-
},
95-
)
84+
for vulnerability in vulnerabilities:
85+
Exploit.objects.update_or_create(
86+
vulnerability_id=vulnerability,
87+
data_source="KEV",
88+
defaults={
89+
"description": kev_vul["shortDescription"],
90+
"date_added": kev_vul["dateAdded"],
91+
"required_action": kev_vul["requiredAction"],
92+
"due_date": kev_vul["dueDate"],
93+
"notes": kev_vul["notes"],
94+
"known_ransomware_campaign_use": True
95+
if kev_vul["knownRansomwareCampaignUse"] == "Known"
96+
else False,
97+
},
98+
)
9699
return 1

vulnerabilities/pipelines/enhance_with_metasploit.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ def add_vulnerability_exploits(self):
6666

6767

6868
def add_vulnerability_exploit(record, logger):
69-
vulnerabilities = set()
7069
references = record.get("references", [])
7170

7271
interesting_references = [
@@ -76,12 +75,11 @@ def add_vulnerability_exploit(record, logger):
7675
if not interesting_references:
7776
return 0
7877

79-
for ref in interesting_references:
80-
try:
81-
if alias := Alias.objects.get(alias=ref):
82-
vulnerabilities.add(alias.vulnerability)
83-
except Alias.DoesNotExist:
84-
continue
78+
vulnerabilities = (
79+
Alias.objects.filter(alias__in=interesting_references, vulnerability__isnull=False)
80+
.values_list("vulnerability", flat=True)
81+
.distinct()
82+
)
8583

8684
if not vulnerabilities:
8785
logger(f"No vulnerability found for aliases {interesting_references}")
@@ -107,7 +105,7 @@ def add_vulnerability_exploit(record, logger):
107105

108106
for vulnerability in vulnerabilities:
109107
Exploit.objects.update_or_create(
110-
vulnerability=vulnerability,
108+
vulnerability_id=vulnerability,
111109
data_source="Metasploit",
112110
defaults={
113111
"description": description,

vulnerabilities/tests/pipelines/test_enhance_with_exploitdb.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,18 @@ def test_exploit_db_improver(mock_get):
4545
# Run Exploit-DB Improver again when there are matching aliases.
4646
improver.execute()
4747
assert Exploit.objects.count() == 1
48+
49+
50+
@pytest.mark.django_db
51+
@mock.patch("requests.get")
52+
def test_invalid_exploit_db_improver(mock_get):
53+
mock_response = Mock(status_code=200)
54+
with open(TEST_DATA, "r") as f:
55+
mock_response.text = f.read()
56+
mock_get.return_value = mock_response
57+
58+
improver = ExploitDBImproverPipeline()
59+
Alias.objects.create(alias="CVE-2009-3699", vulnerability=None)
60+
status, _ = improver.execute()
61+
assert status == 0
62+
assert Exploit.objects.count() == 0

vulnerabilities/tests/pipelines/test_enhance_with_kev.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,18 @@ def test_kev_improver(mock_get):
4545
# Run Kev Improver again when there are matching aliases.
4646
improver.execute()
4747
assert Exploit.objects.count() == 1
48+
49+
50+
@pytest.mark.django_db
51+
@mock.patch("requests.get")
52+
def test_invalid_kev_improver(mock_get):
53+
mock_response = Mock(status_code=200)
54+
mock_response.json.return_value = load_json(TEST_DATA)
55+
mock_get.return_value = mock_response
56+
57+
improver = VulnerabilityKevPipeline()
58+
Alias.objects.create(alias="CVE-2021-38647", vulnerability=None)
59+
60+
status, _ = improver.execute()
61+
assert status == 0
62+
assert Exploit.objects.count() == 0

vulnerabilities/tests/pipelines/test_enhance_with_metasploit.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,17 @@ def test_metasploit_improver(mock_get):
4242
# Run metasploit Improver again when there are matching aliases.
4343
improver.execute()
4444
assert Exploit.objects.count() == 1
45+
46+
47+
@pytest.mark.django_db
48+
@mock.patch("requests.get")
49+
def test_invalid_metasploit_improver(mock_get):
50+
mock_response = Mock(status_code=200)
51+
mock_response.json.return_value = load_json(TEST_DATA)
52+
mock_get.return_value = mock_response
53+
54+
Alias.objects.create(alias="CVE-2007-4387", vulnerability=None) # Alias without vulnerability
55+
improver = MetasploitImproverPipeline()
56+
status, _ = improver.execute()
57+
assert status == 0
58+
assert Exploit.objects.count() == 0

vulnerabilities/tests/pipelines/v2_importers/test_vulnrichment_importer_v2.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@
88
#
99

1010
import json
11-
from pathlib import Path
1211
from unittest.mock import MagicMock
1312
from unittest.mock import patch
1413

1514
import pytest
1615

1716
from vulnerabilities.importer import AdvisoryDataV2
17+
from vulnerabilities.importer import ReferenceV2
1818
from vulnerabilities.importer import VulnerabilitySeverity
1919
from vulnerabilities.pipelines.v2_importers.vulnrichment_importer import VulnrichImporterPipeline
20+
from vulnerabilities.severity_systems import Cvssv4ScoringSystem
2021

2122

2223
@pytest.fixture
@@ -58,8 +59,10 @@ def mock_pathlib(tmp_path):
5859
"metrics": [
5960
{
6061
"cvssV4_0": {
61-
"baseScore": 7.5,
62-
"vectorString": "AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
62+
"version": "4.0",
63+
"baseScore": 5.3,
64+
"baseSeverity": "MEDIUM",
65+
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N",
6366
}
6467
}
6568
],
@@ -103,15 +106,20 @@ def test_collect_advisories(mock_pathlib, mock_vcs_response, mock_fetch_via_vcs)
103106
mock_parse.return_value = AdvisoryDataV2(
104107
advisory_id="CVE-2021-1234",
105108
summary="Sample PyPI vulnerability",
106-
references=[{"url": "https://example.com"}],
109+
references=[ReferenceV2(url="https://example.com")],
107110
affected_packages=[],
108111
weaknesses=[],
109112
url="https://example.com",
110113
severities=[
111114
VulnerabilitySeverity(
112-
system="cvssv4",
113-
value=7.5,
114-
scoring_elements="AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
115+
system=Cvssv4ScoringSystem(
116+
identifier="cvssv4",
117+
name="CVSSv4 Base Score",
118+
url="https://www.first.org/cvss/v4-0/",
119+
notes="CVSSv4 base score and vector",
120+
),
121+
value="5.3",
122+
scoring_elements="CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N",
115123
)
116124
],
117125
)
@@ -126,6 +134,7 @@ def test_collect_advisories(mock_pathlib, mock_vcs_response, mock_fetch_via_vcs)
126134
assert advisory.advisory_id == "CVE-2021-1234"
127135
assert advisory.summary == "Sample PyPI vulnerability"
128136
assert advisory.url == "https://example.com"
137+
assert len(advisory.severities) == 1
129138

130139

131140
def test_clean_downloads(mock_vcs_response, mock_fetch_via_vcs):
@@ -165,8 +174,10 @@ def test_parse_cve_advisory(mock_pathlib, mock_vcs_response, mock_fetch_via_vcs)
165174
"metrics": [
166175
{
167176
"cvssV4_0": {
168-
"baseScore": 7.5,
169-
"vectorString": "AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
177+
"version": "4.0",
178+
"baseScore": 5.3,
179+
"baseSeverity": "MEDIUM",
180+
"vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N",
170181
}
171182
}
172183
],
@@ -185,7 +196,7 @@ def test_parse_cve_advisory(mock_pathlib, mock_vcs_response, mock_fetch_via_vcs)
185196
assert advisory.summary == "Sample PyPI vulnerability"
186197
assert advisory.url == advisory_url
187198
assert len(advisory.severities) == 1
188-
assert advisory.severities[0].value == 7.5
199+
assert advisory.severities[0].value == 5.3
189200

190201

191202
def test_collect_advisories_with_invalid_json(mock_pathlib, mock_vcs_response, mock_fetch_via_vcs):

0 commit comments

Comments
 (0)