Skip to content

Commit c1001df

Browse files
committed
updates for cvss and cve handling
1 parent e09037c commit c1001df

3 files changed

Lines changed: 84 additions & 8 deletions

File tree

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ celery==5.4.0
1919
redis==5.2.1
2020
django-celery-beat==2.7.0
2121
tqdm==4.67.1
22+
cvss==3.4
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 4.2.19 on 2025-03-10 17:36
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('security', '0005_reference_cve_references'),
10+
]
11+
12+
operations = [
13+
migrations.AlterModelOptions(
14+
name='cve',
15+
options={'ordering': ['-cve_id']},
16+
),
17+
migrations.AlterUniqueTogether(
18+
name='cvss',
19+
unique_together={('score', 'severity', 'version', 'vector_string')},
20+
),
21+
]

security/models.py

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import json
1818
import re
19+
from cvss import CVSS2, CVSS3, CVSS4
1920
from time import sleep
2021

2122
from django.db import models
@@ -81,6 +82,9 @@ class CVSS(models.Model):
8182
version = models.DecimalField(max_digits=2, decimal_places=1)
8283
vector_string = models.CharField(max_length=255, blank=True, null=True)
8384

85+
class Meta:
86+
unique_together = ['score', 'severity', 'version', 'vector_string']
87+
8488
def __str__(self):
8589
return f'{self.score} ({self.severity}) [{self.vector_string}]'
8690

@@ -109,8 +113,37 @@ def __str__(self):
109113
def get_absolute_url(self):
110114
return reverse('security:cve_detail', args=[self.cve_id])
111115

116+
def add_cvss_score(self, vector_string, score=None, severity=None, version=None):
117+
if not version:
118+
version = vector_string.split('/')[0].replace('CVSS:', '')
119+
if version.startswith('2'):
120+
cvss_score = CVSS2(vector_string)
121+
elif version.startswith('3'):
122+
cvss_score = CVSS3(vector_string)
123+
elif version.startswith('4'):
124+
cvss_score = CVSS4(vector_string)
125+
if not score:
126+
score = cvss_score.base_score
127+
if not severity:
128+
severity = cvss_score.severities()[0]
129+
existing = self.cvss_scores.filter(version=version, vector_string=vector_string)
130+
if existing:
131+
cvss = existing.first()
132+
else:
133+
cvss, created = CVSS.objects.get_or_create(
134+
version=version,
135+
vector_string=vector_string,
136+
score=score,
137+
severity=severity,
138+
)
139+
cvss.score = score
140+
cvss.severity = severity
141+
cvss.save()
142+
self.cvss_scores.add(cvss)
143+
112144
def fetch_cve_data(self, fetch_nist_data=False, sleep_secs=6):
113145
self.fetch_mitre_cve_data()
146+
self.fetch_osv_dev_cve_data()
114147
if fetch_nist_data:
115148
self.fetch_nist_cve_data()
116149
sleep(sleep_secs) # rate limited, see https://nvd.nist.gov/developers/start-here
@@ -125,6 +158,29 @@ def fetch_mitre_cve_data(self):
125158
cve_json = json.loads(data)
126159
self.parse_mitre_cve_data(cve_json)
127160

161+
def fetch_osv_dev_cve_data(self):
162+
osv_dev_cve_url = f'https://api.osv.dev/v1/vulns/{self.cve_id}'
163+
res = get_url(osv_dev_cve_url)
164+
if res.status_code == 404:
165+
error_message.send(sender=None, text=f'404 - Skipping {self.cve_id} - {osv_dev_cve_url}')
166+
return
167+
data = fetch_content(res, f'Fetching {self.cve_id} OSV data')
168+
cve_json = json.loads(data)
169+
self.parse_osv_dev_cve_data(cve_json)
170+
171+
def parse_osv_dev_cve_data(self, cve_json):
172+
from security.utils import get_or_create_reference
173+
references = cve_json.get('references')
174+
if references:
175+
for reference in references:
176+
ref_type = reference.get('type').capitalize()
177+
url = reference.get('url')
178+
get_or_create_reference(ref_type, url)
179+
scores = cve_json.get('severity')
180+
if scores:
181+
for score in scores:
182+
self.add_cvss_score(vector_string=score.get('score'))
183+
128184
def fetch_nist_cve_data(self):
129185
nist_cve_url = f'https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={self.cve_id}'
130186
res = get_url(nist_cve_url)
@@ -149,13 +205,12 @@ def parse_nist_cve_data(self, cve_json):
149205
for scores in score_data:
150206
for key, value in scores.items():
151207
if key.startswith('cvssData'):
152-
cvss_score, created = CVSS.objects.get_or_create(
208+
self.add_cvss_score(
209+
vector_string=value.get('vectorString'),
153210
score=value.get('baseScore'),
154211
severity=value.get('baseSeverity'),
155-
version=value.get('version'),
156-
vector_string=value.get('vectorString'),
212+
version=value.get('version')
157213
)
158-
self.cvss_scores.add(cvss_score)
159214
references = cve.get('references')
160215
for reference in references:
161216
ref_type = 'Link'
@@ -210,11 +265,10 @@ def parse_mitre_cve_data(self, cve_json):
210265
if metric.get('format') == 'CVSS':
211266
for key, value in metric.items():
212267
if key.startswith('cvss'):
213-
cvss_score, created = CVSS.objects.get_or_create(
268+
self.add_cvss_score(
269+
vector_string=value.get('vectorString'),
214270
score=value.get('baseScore'),
215271
severity=value.get('baseSeverity'),
216-
version=value.get('version'),
217-
vector_string=value.get('vectorString'),
272+
version=value.get('version')
218273
)
219-
self.cvss_scores.add(cvss_score)
220274
self.save()

0 commit comments

Comments
 (0)