Skip to content

Commit 684082e

Browse files
committed
Add epss to severity scoring
Add published_at date to the Vulnerability score model. Add EPSS importer Signed-off-by: ziadhany <[email protected]>
1 parent 9c12d56 commit 684082e

File tree

12 files changed

+145
-229
lines changed

12 files changed

+145
-229
lines changed

vulnerabilities/api.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
from rest_framework.throttling import AnonRateThrottle
2525
from rest_framework.throttling import UserRateThrottle
2626

27-
from vulnerabilities.models import EPSS
2827
from vulnerabilities.models import Alias
2928
from vulnerabilities.models import Package
3029
from vulnerabilities.models import Vulnerability
@@ -38,7 +37,14 @@
3837
class VulnerabilitySeveritySerializer(serializers.ModelSerializer):
3938
class Meta:
4039
model = VulnerabilitySeverity
41-
fields = ["value", "scoring_system", "scoring_elements"]
40+
fields = ["value", "scoring_system", "scoring_elements", "published_at"]
41+
42+
def to_representation(self, instance):
43+
data = super().to_representation(instance)
44+
published_at = data.get("published_at", None)
45+
if not published_at:
46+
data.pop("published_at")
47+
return data
4248

4349

4450
class VulnerabilityReferenceSerializer(serializers.ModelSerializer):
@@ -168,12 +174,6 @@ def to_representation(self, instance):
168174
return representation
169175

170176

171-
class EPSSSerializer(serializers.ModelSerializer):
172-
class Meta:
173-
model = EPSS
174-
fields = ["score", "percentile", "created_at", "updated_at"]
175-
176-
177177
class VulnerabilitySerializer(BaseResourceSerializer):
178178
fixed_packages = MinimalPackageSerializer(
179179
many=True, source="filtered_fixed_packages", read_only=True
@@ -182,7 +182,6 @@ class VulnerabilitySerializer(BaseResourceSerializer):
182182

183183
references = VulnerabilityReferenceSerializer(many=True, source="vulnerabilityreference_set")
184184
aliases = AliasSerializer(many=True, source="alias")
185-
epss = EPSSSerializer(read_only=True)
186185
weaknesses = WeaknessSerializer(many=True)
187186

188187
def to_representation(self, instance):
@@ -191,10 +190,6 @@ def to_representation(self, instance):
191190
weaknesses = data.get("weaknesses", [])
192191
data["weaknesses"] = [weakness for weakness in weaknesses if weakness is not None]
193192

194-
epss = data.get("epss", None)
195-
if not epss:
196-
data.pop("epss")
197-
198193
return data
199194

200195
class Meta:
@@ -208,7 +203,6 @@ class Meta:
208203
"affected_packages",
209204
"references",
210205
"weaknesses",
211-
"epss",
212206
]
213207

214208

vulnerabilities/import_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ def process_inferences(inferences: List[Inference], advisory: Advisory, improver
189189
defaults={
190190
"value": str(severity.value),
191191
"scoring_elements": str(severity.scoring_elements),
192+
"published_at": str(severity.published_at),
192193
},
193194
)
194195
if updated:

vulnerabilities/importer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,17 @@ class VulnerabilitySeverity:
5252
system: ScoringSystem
5353
value: str
5454
scoring_elements: str = ""
55+
published_at: Optional[datetime.datetime] = None
5556

5657
def to_dict(self):
58+
published_at_dict = (
59+
{"published_at": self.published_at.isoformat()} if self.published_at else {}
60+
)
5761
return {
5862
"system": self.system.identifier,
5963
"value": self.value,
6064
"scoring_elements": self.scoring_elements,
65+
**published_at_dict,
6166
}
6267

6368
@classmethod
@@ -70,6 +75,7 @@ def from_dict(cls, severity: dict):
7075
system=SCORING_SYSTEMS[severity["system"]],
7176
value=severity["value"],
7277
scoring_elements=severity.get("scoring_elements", ""),
78+
published_at=severity.get("published_at"),
7379
)
7480

7581

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from vulnerabilities.importers import debian
1616
from vulnerabilities.importers import debian_oval
1717
from vulnerabilities.importers import elixir_security
18+
from vulnerabilities.importers import epss
1819
from vulnerabilities.importers import fireeye
1920
from vulnerabilities.importers import gentoo
2021
from vulnerabilities.importers import github
@@ -71,6 +72,7 @@
7172
oss_fuzz.OSSFuzzImporter,
7273
ruby.RubyImporter,
7374
github_osv.GithubOSVImporter,
75+
epss.EPSSImporter,
7476
]
7577

7678
IMPORTERS_REGISTRY = {x.qualified_name: x for x in IMPORTERS_REGISTRY}

vulnerabilities/importers/epss.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/nexB/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
import csv
10+
import gzip
11+
import logging
12+
import urllib.request
13+
from datetime import datetime
14+
from typing import Iterable
15+
16+
from vulnerabilities import severity_systems
17+
from vulnerabilities.importer import AdvisoryData
18+
from vulnerabilities.importer import Importer
19+
from vulnerabilities.importer import Reference
20+
from vulnerabilities.importer import VulnerabilitySeverity
21+
22+
logger = logging.getLogger(__name__)
23+
24+
25+
class EPSSImporter(Importer):
26+
"""Exploit Prediction Scoring System (EPSS) Importer"""
27+
28+
advisory_url = "https://epss.cyentia.com/epss_scores-current.csv.gz"
29+
spdx_license_expression = "unknown"
30+
importer_name = "EPSS Importer"
31+
32+
def advisory_data(self) -> Iterable[AdvisoryData]:
33+
response = urllib.request.urlopen(self.advisory_url)
34+
with gzip.open(response, "rb") as f:
35+
lines = [l.decode("utf-8") for l in f.readlines()]
36+
37+
epss_reader = csv.reader(lines)
38+
model_version, score_date = next(
39+
epss_reader
40+
) # score_date='score_date:2024-05-19T00:00:00+0000'
41+
published_at = datetime.strptime(score_date[11::], "%Y-%m-%dT%H:%M:%S%z")
42+
43+
next(epss_reader) # skip the header row
44+
for epss_row in epss_reader:
45+
cve, score, percentile = epss_row
46+
47+
if not cve or not score or not percentile:
48+
logger.error(f"Invalid epss row: {epss_row}")
49+
continue
50+
51+
severity = VulnerabilitySeverity(
52+
system=severity_systems.EPSS,
53+
value=score,
54+
scoring_elements=percentile,
55+
published_at=published_at,
56+
)
57+
58+
references = Reference(
59+
url=f"https://api.first.org/data/v1/epss?cve={cve}",
60+
severities=[severity],
61+
)
62+
63+
yield AdvisoryData(
64+
aliases=[cve],
65+
references=[references],
66+
url=self.advisory_url,
67+
)

vulnerabilities/improvers/__init__.py

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

1010
from vulnerabilities.improvers import valid_versions
11-
from vulnerabilities.improvers import vulnerability_epss
1211
from vulnerabilities.improvers import vulnerability_status
1312

1413
IMPROVERS_REGISTRY = [
@@ -27,7 +26,6 @@
2726
valid_versions.OSSFuzzImprover,
2827
valid_versions.RubyImprover,
2928
valid_versions.GithubOSVImprover,
30-
vulnerability_epss.EPSSImprover,
3129
vulnerability_status.VulnerabilityStatusImprover,
3230
]
3331

vulnerabilities/improvers/vulnerability_epss.py

Lines changed: 0 additions & 67 deletions
This file was deleted.

vulnerabilities/migrations/0057_epss.py

Lines changed: 0 additions & 57 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Generated by Django 4.1.13 on 2024-05-22 21:51
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("vulnerabilities", "0056_alter_packagechangelog_software_version_and_more"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="vulnerabilityseverity",
15+
name="published_at",
16+
field=models.DateTimeField(
17+
blank=True,
18+
help_text="UTC Date of publication of the vulnerability severity",
19+
null=True,
20+
),
21+
),
22+
migrations.AlterField(
23+
model_name="vulnerabilityseverity",
24+
name="scoring_system",
25+
field=models.CharField(
26+
choices=[
27+
("cvssv2", "CVSSv2 Base Score"),
28+
("cvssv3", "CVSSv3 Base Score"),
29+
("cvssv3.1", "CVSSv3.1 Base Score"),
30+
("rhbs", "RedHat Bugzilla severity"),
31+
("rhas", "RedHat Aggregate severity"),
32+
("archlinux", "Archlinux Vulnerability Group Severity"),
33+
("cvssv3.1_qr", "CVSSv3.1 Qualitative Severity Rating"),
34+
("generic_textual", "Generic textual severity rating"),
35+
("apache_httpd", "Apache Httpd Severity"),
36+
("apache_tomcat", "Apache Tomcat Severity"),
37+
("epss", "Exploit Prediction Scoring System"),
38+
],
39+
help_text="Identifier for the scoring system used. Available choices are: cvssv2: CVSSv2 Base Score,\ncvssv3: CVSSv3 Base Score,\ncvssv3.1: CVSSv3.1 Base Score,\nrhbs: RedHat Bugzilla severity,\nrhas: RedHat Aggregate severity,\narchlinux: Archlinux Vulnerability Group Severity,\ncvssv3.1_qr: CVSSv3.1 Qualitative Severity Rating,\ngeneric_textual: Generic textual severity rating,\napache_httpd: Apache Httpd Severity,\napache_tomcat: Apache Tomcat Severity,\nepss: Exploit Prediction Scoring System ",
40+
max_length=50,
41+
),
42+
),
43+
]

0 commit comments

Comments
 (0)