Skip to content

Commit c311f73

Browse files
authored
Add Known Exploited Vulnerabilities improver (#1422)
Add a Kev test & function docstrings Change the kev model ( known_ransomware_campaign_use from integer choices to boolean ) Add kev to api Access the vulnerability directly from Alias Add a tooltips and edit kev table Add a kev in a separate tab Solve migration conflict Add a basic improver squash migration files Add a basic Known Exploited Vulnerabilities model Signed-off-by: ziadhany <[email protected]>
1 parent f16228e commit c311f73

File tree

8 files changed

+361
-2
lines changed

8 files changed

+361
-2
lines changed

vulnerabilities/api.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from rest_framework.throttling import UserRateThrottle
2626

2727
from vulnerabilities.models import Alias
28+
from vulnerabilities.models import Kev
2829
from vulnerabilities.models import Package
2930
from vulnerabilities.models import Vulnerability
3031
from vulnerabilities.models import VulnerabilityReference
@@ -167,6 +168,12 @@ def to_representation(self, instance):
167168
return representation
168169

169170

171+
class KEVSerializer(serializers.ModelSerializer):
172+
class Meta:
173+
model = Kev
174+
fields = ["date_added", "description", "required_action", "due_date", "resources_and_notes"]
175+
176+
170177
class VulnerabilitySerializer(BaseResourceSerializer):
171178
fixed_packages = MinimalPackageSerializer(
172179
many=True, source="filtered_fixed_packages", read_only=True
@@ -175,6 +182,7 @@ class VulnerabilitySerializer(BaseResourceSerializer):
175182

176183
references = VulnerabilityReferenceSerializer(many=True, source="vulnerabilityreference_set")
177184
aliases = AliasSerializer(many=True, source="alias")
185+
kev = KEVSerializer(read_only=True)
178186
weaknesses = WeaknessSerializer(many=True)
179187

180188
def to_representation(self, instance):
@@ -183,6 +191,10 @@ def to_representation(self, instance):
183191
weaknesses = data.get("weaknesses", [])
184192
data["weaknesses"] = [weakness for weakness in weaknesses if weakness is not None]
185193

194+
kev = data.get("kev", None)
195+
if not kev:
196+
data.pop("kev")
197+
186198
return data
187199

188200
class Meta:
@@ -196,6 +208,7 @@ class Meta:
196208
"affected_packages",
197209
"references",
198210
"weaknesses",
211+
"kev",
199212
]
200213

201214

vulnerabilities/improvers/__init__.py

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

1010
from vulnerabilities.improvers import valid_versions
11+
from vulnerabilities.improvers import vulnerability_kev
1112
from vulnerabilities.improvers import vulnerability_status
1213

1314
IMPROVERS_REGISTRY = [
@@ -27,6 +28,7 @@
2728
valid_versions.RubyImprover,
2829
valid_versions.GithubOSVImprover,
2930
vulnerability_status.VulnerabilityStatusImprover,
31+
vulnerability_kev.VulnerabilityKevImprover,
3032
]
3133

3234
IMPROVERS_REGISTRY = {x.qualified_name: x for x in IMPROVERS_REGISTRY}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import logging
2+
from typing import Iterable
3+
4+
from django.db.models import QuerySet
5+
from sphinx.util import requests
6+
7+
from vulnerabilities.improver import Improver
8+
from vulnerabilities.improver import Inference
9+
from vulnerabilities.models import Advisory
10+
from vulnerabilities.models import Alias
11+
from vulnerabilities.models import Kev
12+
13+
logger = logging.getLogger(__name__)
14+
15+
16+
class VulnerabilityKevImprover(Improver):
17+
"""
18+
Known Exploited Vulnerabilities Improver
19+
"""
20+
21+
@property
22+
def interesting_advisories(self) -> QuerySet:
23+
# TODO Modify KEV improver to iterate over the vulnerabilities alias, not the advisory
24+
return [Advisory.objects.first()]
25+
26+
def get_inferences(self, advisory_data) -> Iterable[Inference]:
27+
"""
28+
Fetch Kev data, iterate over it to find the vulnerability with the specified alias, and create or update
29+
the Kev instance accordingly.
30+
"""
31+
32+
kev_url = (
33+
"https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
34+
)
35+
response = requests.get(kev_url)
36+
kev_data = response.json()
37+
if response.status_code != 200:
38+
logger.error(
39+
f"Failed to fetch the CISA Catalog of Known Exploited Vulnerabilities: {kev_url}"
40+
)
41+
return []
42+
43+
for kev_vul in kev_data.get("vulnerabilities", []):
44+
alias = Alias.objects.get_or_none(alias=kev_vul["cveID"])
45+
if not alias:
46+
continue
47+
48+
vul = alias.vulnerability
49+
50+
if not vul:
51+
continue
52+
53+
Kev.objects.update_or_create(
54+
vulnerability=vul,
55+
defaults={
56+
"description": kev_vul["shortDescription"],
57+
"date_added": kev_vul["dateAdded"],
58+
"required_action": kev_vul["requiredAction"],
59+
"due_date": kev_vul["dueDate"],
60+
"resources_and_notes": kev_vul["notes"],
61+
"known_ransomware_campaign_use": True
62+
if kev_vul["knownRansomwareCampaignUse"] == "Known"
63+
else False,
64+
},
65+
)
66+
return []
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Generated by Django 4.1.13 on 2024-05-29 19:14
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("vulnerabilities", "0056_alter_packagechangelog_software_version_and_more"),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name="Kev",
16+
fields=[
17+
(
18+
"id",
19+
models.AutoField(
20+
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
21+
),
22+
),
23+
(
24+
"date_added",
25+
models.DateField(
26+
blank=True,
27+
help_text="The date the vulnerability was added to the Known Exploited Vulnerabilities (KEV) catalog in the format YYYY-MM-DD.",
28+
null=True,
29+
),
30+
),
31+
(
32+
"description",
33+
models.TextField(
34+
help_text="Description of the vulnerability in the Known Exploited Vulnerabilities (KEV) catalog, usually a refinement of the original CVE description"
35+
),
36+
),
37+
(
38+
"required_action",
39+
models.TextField(
40+
help_text="The required action to address the vulnerability, typically to apply vendor updates or apply vendor mitigations or to discontinue use."
41+
),
42+
),
43+
(
44+
"due_date",
45+
models.DateField(
46+
help_text="The date the required action is due in the format YYYY-MM-DD,which applies to all USA federal civilian executive branch (FCEB) agencies,but all organizations are strongly encouraged to execute the required action."
47+
),
48+
),
49+
(
50+
"resources_and_notes",
51+
models.TextField(
52+
help_text="Additional notes and resources about the vulnerability, often a URL to vendor instructions."
53+
),
54+
),
55+
(
56+
"known_ransomware_campaign_use",
57+
models.BooleanField(
58+
default=False,
59+
help_text="Known if this vulnerability is known to have been leveraged as part of a ransomware campaign; \n or 'Unknown' if CISA lacks confirmation that the vulnerability has been utilized for ransomware.",
60+
),
61+
),
62+
(
63+
"vulnerability",
64+
models.OneToOneField(
65+
on_delete=django.db.models.deletion.CASCADE,
66+
related_name="kev",
67+
to="vulnerabilities.vulnerability",
68+
),
69+
),
70+
],
71+
),
72+
]

vulnerabilities/models.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,6 @@ class Meta:
11211121

11221122

11231123
class ChangeLog(models.Model):
1124-
11251124
action_time = models.DateTimeField(
11261125
# check if dates are actually UTC
11271126
default=timezone.now,
@@ -1261,7 +1260,6 @@ def log_action(self, package, action_type, actor_name, source_url, related_vulne
12611260

12621261

12631262
class PackageChangeLog(ChangeLog):
1264-
12651263
AFFECTED_BY = 1
12661264
FIXING = 2
12671265

@@ -1309,3 +1307,53 @@ def log_fixing(cls, package, importer, source_url, related_vulnerability):
13091307
source_url=source_url,
13101308
related_vulnerability=related_vulnerability,
13111309
)
1310+
1311+
1312+
class Kev(models.Model):
1313+
"""
1314+
Known Exploited Vulnerabilities
1315+
"""
1316+
1317+
vulnerability = models.OneToOneField(
1318+
Vulnerability,
1319+
on_delete=models.CASCADE,
1320+
related_name="kev",
1321+
)
1322+
1323+
date_added = models.DateField(
1324+
help_text="The date the vulnerability was added to the Known Exploited Vulnerabilities"
1325+
" (KEV) catalog in the format YYYY-MM-DD.",
1326+
null=True,
1327+
blank=True,
1328+
)
1329+
1330+
description = models.TextField(
1331+
help_text="Description of the vulnerability in the Known Exploited Vulnerabilities"
1332+
" (KEV) catalog, usually a refinement of the original CVE description"
1333+
)
1334+
1335+
required_action = models.TextField(
1336+
help_text="The required action to address the vulnerability, typically to "
1337+
"apply vendor updates or apply vendor mitigations or to discontinue use."
1338+
)
1339+
1340+
due_date = models.DateField(
1341+
help_text="The date the required action is due in the format YYYY-MM-DD,"
1342+
"which applies to all USA federal civilian executive branch (FCEB) agencies,"
1343+
"but all organizations are strongly encouraged to execute the required action."
1344+
)
1345+
1346+
resources_and_notes = models.TextField(
1347+
help_text="Additional notes and resources about the vulnerability,"
1348+
" often a URL to vendor instructions."
1349+
)
1350+
1351+
known_ransomware_campaign_use = models.BooleanField(
1352+
default=False,
1353+
help_text="""Known if this vulnerability is known to have been leveraged as part of a ransomware campaign;
1354+
or 'Unknown' if CISA lacks confirmation that the vulnerability has been utilized for ransomware.""",
1355+
)
1356+
1357+
@property
1358+
def get_known_ransomware_campaign_use_type(self):
1359+
return "Known" if self.known_ransomware_campaign_use else "Unknown"

vulnerabilities/templates/vulnerability_details.html

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@
6060
</span>
6161
</a>
6262
</li>
63+
{% if vulnerability.kev %}<li data-tab="known-exploited-vulnerabilities">
64+
<a>
65+
<span>
66+
Known Exploited Vulnerabilities
67+
</span>
68+
</a>
69+
</li>{% endif %}
6370
<li data-tab="history">
6471
<a>
6572
<span>
@@ -374,6 +381,89 @@
374381
</tr>
375382
{% endfor %}
376383
</div>
384+
{% if vulnerability.kev %}
385+
<div class="tab-div content" data-content="known-exploited-vulnerabilities">
386+
<div class="has-text-weight-bold tab-nested-div ml-1 mb-1 mt-1">
387+
Known Exploited Vulnerabilities
388+
</div>
389+
<table class="table vcio-table width-100-pct mt-2">
390+
<tbody>
391+
<tr>
392+
<td class="two-col-left">
393+
<span class="has-tooltip-multiline has-tooltip-black has-tooltip-arrow has-tooltip-text-left"
394+
data-tooltip="'Known' if this vulnerability is known to have been leveraged as part of a ransomware campaign; 'Unknown' if CISA lacks confirmation that the vulnerability has been utilized for ransomware">
395+
Known Ransomware Campaign Use:
396+
</span>
397+
</td>
398+
<td class="two-col-right">{{ vulnerability.kev.get_known_ransomware_campaign_use_type }}</td>
399+
</tr>
400+
401+
{% if vulnerability.kev.description %}
402+
<tr>
403+
<td class="two-col-left">
404+
<span class="has-tooltip-multiline has-tooltip-black has-tooltip-arrow has-tooltip-text-left"
405+
data-tooltip="Description of the vulnerability in the Known Exploited Vulnerabilities
406+
(KEV) catalog, usually a refinement of the original CVE description.">
407+
Description:
408+
</span>
409+
</td>
410+
<td class="two-col-right">{{ vulnerability.kev.description }}</td>
411+
</tr>
412+
{% endif %}
413+
{% if vulnerability.kev.required_action %}
414+
<tr>
415+
<td class="two-col-left">
416+
<span class="has-tooltip-multiline has-tooltip-black has-tooltip-arrow has-tooltip-text-left"
417+
data-tooltip="The required action to address the vulnerability">
418+
Required Action:
419+
</span>
420+
</td>
421+
<td class="two-col-right">{{ vulnerability.kev.required_action }}</td>
422+
</tr>
423+
{% endif %}
424+
425+
{% if vulnerability.kev.resources_and_notes %}
426+
<tr>
427+
<td class="two-col-left">
428+
<span class="has-tooltip-multiline has-tooltip-black has-tooltip-arrow has-tooltip-text-left"
429+
data-tooltip="Any additional notes about the vulnerability">
430+
Notes:
431+
</span>
432+
</td>
433+
<td class="two-col-right">{{ vulnerability.kev.resources_and_notes }}</td>
434+
</tr>
435+
{% endif %}
436+
437+
{% if vulnerability.kev.due_date %}
438+
<tr>
439+
<td class="two-col-left">
440+
<span class="has-tooltip-multiline has-tooltip-black has-tooltip-arrow has-tooltip-text-left"
441+
data-tooltip="The date the required action is due in the format YYYY-MM-DD">
442+
Due Date:
443+
</span>
444+
</td>
445+
<td class="two-col-right">{{ vulnerability.kev.due_date }}</td>
446+
</tr>
447+
{% endif %}
448+
{% if vulnerability.kev.date_added %}
449+
<tr>
450+
<td class="two-col-left">
451+
<span
452+
class="has-tooltip-multiline has-tooltip-black has-tooltip-arrow has-tooltip-text-left"
453+
data-tooltip="The date vulnerability was added to the catalog in the format YYYY-MM-DD">
454+
Date Added:
455+
</span>
456+
</td>
457+
<td class="two-col-right">{{ vulnerability.kev.date_added }}</td>
458+
</tr>
459+
{% endif %}
460+
461+
</tbody>
462+
</table>
463+
{% endif %}
464+
465+
</div>
466+
377467
<div class="tab-div content" data-content="history">
378468
<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
379469
<thead>

0 commit comments

Comments
 (0)