Skip to content

Commit 1625418

Browse files
authored
Merge branch 'main' into api-commit-patch
2 parents 3eab3e9 + 73b2f75 commit 1625418

12 files changed

Lines changed: 474 additions & 215 deletions
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Generated by Django 5.2.11 on 2026-06-11 13:01
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("vulnerabilities", "0134_advisoryv2__all_impacts_unfurled_at_and_more"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="advisoryexploit",
15+
name="record_id",
16+
field=models.CharField(
17+
blank=True,
18+
help_text="The unique identifier for the exploit record in the original upstream data source, such as the CISA KEV ID or the exploitdb ID.",
19+
max_length=255,
20+
null=True,
21+
),
22+
),
23+
migrations.AddConstraint(
24+
model_name="advisoryexploit",
25+
constraint=models.UniqueConstraint(
26+
fields=("advisory", "data_source", "record_id"),
27+
name="unique_advisory_exploit_source",
28+
),
29+
),
30+
]

vulnerabilities/models.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3959,10 +3959,25 @@ class AdvisoryExploit(models.Model):
39593959
help_text="The URL to the exploit as provided in the original upstream data source.",
39603960
)
39613961

3962+
record_id = models.CharField(
3963+
null=True,
3964+
blank=True,
3965+
max_length=255,
3966+
help_text="The unique identifier for the exploit record in the original upstream data source, such as the CISA KEV ID or the exploitdb ID.",
3967+
)
3968+
39623969
@property
39633970
def get_known_ransomware_campaign_use_type(self):
39643971
return "Known" if self.known_ransomware_campaign_use else "Unknown"
39653972

3973+
class Meta:
3974+
constraints = [
3975+
models.UniqueConstraint(
3976+
fields=["advisory", "data_source", "record_id"],
3977+
name="unique_advisory_exploit_source",
3978+
)
3979+
]
3980+
39663981

39673982
class SSVC(models.Model):
39683983
vector = models.CharField(max_length=255, help_text="The vector string representing the SSVC.")

vulnerabilities/pipelines/v2_improvers/enhance_with_exploitdb.py

Lines changed: 68 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,10 @@
1515
import requests
1616
from aboutcode.pipeline import LoopProgress
1717
from dateutil import parser as dateparser
18-
from django.db import DataError
1918

20-
from vulnerabilities.models import AdvisoryAlias
2119
from vulnerabilities.models import AdvisoryExploit
22-
from vulnerabilities.models import AdvisoryReference
23-
from vulnerabilities.models import AdvisoryV2
2420
from vulnerabilities.pipelines import VulnerableCodePipeline
21+
from vulnerabilities.utils import build_alias_to_advisory_map
2522

2623

2724
class ExploitDBImproverPipeline(VulnerableCodePipeline):
@@ -65,96 +62,80 @@ def add_exploit(self):
6562
raw_data = list(csvreader)
6663
fetched_exploit_count = len(raw_data)
6764

68-
vulnerability_exploit_count = 0
6965
self.log(f"Enhancing the vulnerability with {fetched_exploit_count:,d} exploit records")
7066
progress = LoopProgress(total_iterations=fetched_exploit_count, logger=self.log)
7167

72-
for row in progress.iter(raw_data):
73-
vulnerability_exploit_count += add_vulnerability_exploit(row, self.log)
74-
75-
self.log(f"Successfully added {vulnerability_exploit_count:,d} exploit-db advisory exploit")
76-
68+
all_aliases = set()
7769

78-
def add_vulnerability_exploit(row, logger):
79-
advisories = set()
70+
for row in raw_data:
71+
if row["codes"]:
72+
all_aliases.update(row["codes"].split(";"))
8073

81-
aliases = row["codes"].split(";") if row["codes"] else []
74+
alias_to_advisories = build_alias_to_advisory_map(all_aliases)
8275

83-
if not aliases:
84-
return 0
76+
exploits = []
77+
seen = set()
8578

86-
for raw_alias in aliases:
87-
try:
88-
if alias := AdvisoryAlias.objects.get(alias=raw_alias):
89-
for adv in alias.advisories.all():
90-
advisories.add(adv)
91-
else:
92-
advs = AdvisoryV2.objects.filter(advisory_id=raw_alias).latest_per_avid()
93-
for adv in advs:
94-
advisories.add(adv)
95-
except AdvisoryAlias.DoesNotExist:
96-
continue
97-
98-
if not advisories:
99-
logger(f"No advisory found for aliases {aliases}")
100-
return 0
101-
102-
date_added = parse_date(row["date_added"])
103-
source_date_published = parse_date(row["date_published"])
104-
source_date_updated = parse_date(row["date_updated"])
105-
106-
for advisory in advisories:
107-
add_exploit_references(row["codes"], row["source_url"], row["file"], advisory, logger)
108-
try:
109-
AdvisoryExploit.objects.update_or_create(
110-
advisory=advisory,
111-
data_source="Exploit-DB",
112-
defaults={
113-
"date_added": date_added,
114-
"description": row["description"],
115-
"known_ransomware_campaign_use": row["verified"],
116-
"source_date_published": source_date_published,
117-
"exploit_type": row["type"],
118-
"platform": row["platform"],
119-
"source_date_updated": source_date_updated,
120-
"source_url": row["source_url"],
121-
},
122-
)
123-
except DataError as e:
124-
logger(
125-
f"Failed to Create the Vulnerability Exploit-DB with error {e!r}:\n{traceback_format_exc()}",
126-
level=logging.ERROR,
127-
)
128-
return 1
129-
130-
131-
def add_exploit_references(ref_id, direct_url, path, adv, logger):
132-
url_map = {
133-
"file_url": f"https://gitlab.com/exploit-database/exploitdb/-/blob/main/{path}",
134-
"direct_url": direct_url,
135-
}
136-
137-
for key, url in url_map.items():
138-
if url:
139-
try:
140-
ref, created = AdvisoryReference.objects.update_or_create(
141-
url=url,
142-
defaults={
143-
"reference_id": ref_id,
144-
"reference_type": AdvisoryReference.EXPLOIT,
145-
},
146-
)
147-
148-
if created:
149-
ref.advisories.add(adv)
150-
ref.save()
151-
logger(f"Created {ref} for {adv} with {key}={url}")
152-
153-
except DataError as e:
154-
logger(
155-
f"Failed to Create the Vulnerability Reference For Exploit-DB with error {e!r}:\n{traceback_format_exc()}",
156-
level=logging.ERROR,
157-
)
79+
for row in progress.iter(raw_data):
80+
aliases = row["codes"].split(";") if row["codes"] else []
81+
82+
if not aliases:
83+
continue
84+
85+
date_added = parse_date(row["date_added"])
86+
source_date_published = parse_date(row["date_published"])
87+
source_date_updated = parse_date(row["date_updated"])
88+
89+
for alias in aliases:
90+
for advisory in alias_to_advisories.get(alias, ()):
91+
92+
key = (
93+
advisory.id,
94+
"Exploit-DB",
95+
alias,
96+
)
97+
98+
if key in seen:
99+
continue
100+
101+
seen.add(key)
102+
103+
exploits.append(
104+
AdvisoryExploit(
105+
advisory=advisory,
106+
record_id=alias,
107+
data_source="Exploit-DB",
108+
date_added=date_added,
109+
description=row["description"],
110+
known_ransomware_campaign_use=row["verified"],
111+
source_date_published=source_date_published,
112+
exploit_type=row["type"],
113+
platform=row["platform"],
114+
source_date_updated=source_date_updated,
115+
source_url=row["source_url"],
116+
)
117+
)
118+
119+
AdvisoryExploit.objects.bulk_create(
120+
exploits,
121+
update_conflicts=True,
122+
unique_fields=[
123+
"advisory",
124+
"data_source",
125+
"record_id",
126+
],
127+
update_fields=[
128+
"date_added",
129+
"description",
130+
"known_ransomware_campaign_use",
131+
"source_date_published",
132+
"exploit_type",
133+
"platform",
134+
"source_date_updated",
135+
"source_url",
136+
],
137+
batch_size=1000,
138+
)
158139

159140

160141
def parse_date(date_string):

vulnerabilities/pipelines/v2_improvers/enhance_with_github_poc.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from vulnerabilities.models import AdvisoryPOC
1818
from vulnerabilities.models import AdvisoryV2
1919
from vulnerabilities.pipelines import VulnerableCodePipeline
20+
from vulnerabilities.utils import relate_aliases_with_advisories
2021

2122

2223
class GithubPocsImproverPipeline(VulnerableCodePipeline):
@@ -61,18 +62,7 @@ def collect_and_store_exploits(self):
6162

6263
filename = file_path.stem.strip()
6364

64-
advisories = set()
65-
try:
66-
if alias := AdvisoryAlias.objects.get(alias=filename):
67-
for adv in alias.advisories.all():
68-
advisories.add(adv)
69-
else:
70-
advs = AdvisoryV2.objects.filter(advisory_id=filename).latest_per_avid()
71-
for adv in advs:
72-
advisories.add(adv)
73-
except AdvisoryAlias.DoesNotExist:
74-
self.log(f"Advisory {filename} not found.")
75-
continue
65+
advisories = relate_aliases_with_advisories([filename])
7666

7767
for advisory in advisories:
7868
for exploit_data in exploits_data:

vulnerabilities/pipelines/v2_improvers/enhance_with_kev.py

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@
1313
import requests
1414
from aboutcode.pipeline import LoopProgress
1515

16-
from vulnerabilities.models import AdvisoryAlias
1716
from vulnerabilities.models import AdvisoryExploit
18-
from vulnerabilities.models import AdvisoryV2
1917
from vulnerabilities.pipelines import VulnerableCodePipeline
18+
from vulnerabilities.utils import build_alias_to_advisory_map
2019

2120

2221
class VulnerabilityKevPipeline(VulnerableCodePipeline):
@@ -53,51 +52,56 @@ def fetch_exploits(self):
5352
def add_exploits(self):
5453
fetched_exploit_count = self.kev_data.get("count")
5554
self.log(f"Enhancing the vulnerability with {fetched_exploit_count:,d} exploit records")
56-
57-
vulnerability_exploit_count = 0
5855
progress = LoopProgress(total_iterations=fetched_exploit_count, logger=self.log)
59-
60-
for record in progress.iter(self.kev_data.get("vulnerabilities", [])):
61-
vulnerability_exploit_count += add_vulnerability_exploit(
62-
kev_vul=record,
63-
logger=self.log,
64-
)
65-
66-
self.log(f"Successfully added {vulnerability_exploit_count:,d} kev exploit")
67-
68-
69-
def add_vulnerability_exploit(kev_vul, logger):
70-
cve_id = kev_vul.get("cveID")
71-
72-
if not cve_id:
73-
return 0
74-
75-
advisories = set()
76-
try:
77-
if alias := AdvisoryAlias.objects.get(alias=cve_id):
78-
for adv in alias.advisories.all():
79-
advisories.add(adv)
80-
else:
81-
advs = AdvisoryV2.objects.filter(advisory_id=cve_id).latest_per_avid()
82-
for adv in advs:
83-
advisories.add(adv)
84-
except AdvisoryAlias.DoesNotExist:
85-
logger(f"No advisory found for aliases {cve_id}")
86-
return 0
87-
88-
for advisory in advisories:
89-
AdvisoryExploit.objects.update_or_create(
90-
advisory=advisory,
91-
data_source="KEV",
92-
defaults={
93-
"description": kev_vul["shortDescription"],
94-
"date_added": kev_vul["dateAdded"],
95-
"required_action": kev_vul["requiredAction"],
96-
"due_date": kev_vul["dueDate"],
97-
"notes": kev_vul["notes"],
98-
"known_ransomware_campaign_use": (
99-
True if kev_vul["knownRansomwareCampaignUse"] == "Known" else False
100-
),
101-
},
56+
cve_ids = {
57+
record["cveID"] for record in self.kev_data["vulnerabilities"] if record.get("cveID")
58+
}
59+
60+
cve_to_advisories = build_alias_to_advisory_map(cve_ids)
61+
62+
exploits = []
63+
64+
advisories_seen_multiple_times = set()
65+
66+
for record in progress.iter(self.kev_data["vulnerabilities"]):
67+
cve_id = record.get("cveID")
68+
69+
if not cve_id:
70+
continue
71+
72+
for advisory in cve_to_advisories.get(cve_id, []):
73+
if (advisory.avid, cve_id) in advisories_seen_multiple_times:
74+
continue
75+
advisories_seen_multiple_times.add((advisory.avid, cve_id))
76+
exploits.append(
77+
AdvisoryExploit(
78+
advisory=advisory,
79+
record_id=cve_id,
80+
data_source="KEV",
81+
description=record["shortDescription"],
82+
date_added=record["dateAdded"],
83+
required_action=record["requiredAction"],
84+
due_date=record["dueDate"],
85+
notes=record["notes"],
86+
known_ransomware_campaign_use=(
87+
record["knownRansomwareCampaignUse"] == "Known"
88+
),
89+
)
90+
)
91+
if not exploits:
92+
return
93+
94+
AdvisoryExploit.objects.bulk_create(
95+
exploits,
96+
update_conflicts=True,
97+
unique_fields=["advisory", "data_source", "record_id"],
98+
update_fields=[
99+
"description",
100+
"date_added",
101+
"required_action",
102+
"due_date",
103+
"notes",
104+
"known_ransomware_campaign_use",
105+
],
106+
batch_size=1000,
102107
)
103-
return 1

0 commit comments

Comments
 (0)