Skip to content

Commit 86f6927

Browse files
committed
Optimize performance, refactor, and rename the add_vulnerability_risk_score function. Rename the help text for the model.
Signed-off-by: ziad hany <[email protected]>
1 parent 530cb52 commit 86f6927

File tree

7 files changed

+96
-52
lines changed

7 files changed

+96
-52
lines changed

vulnerabilities/migrations/0077_vulnerability_exploitability_and_more.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2.16 on 2024-11-08 14:07
1+
# Generated by Django 4.2.16 on 2024-11-12 12:16
22

33
from django.db import migrations, models
44

@@ -15,7 +15,9 @@ class Migration(migrations.Migration):
1515
name="exploitability",
1616
field=models.DecimalField(
1717
decimal_places=2,
18-
help_text="Exploitability refers to the potential or probability of a software package vulnerability being \n exploited by malicious actors to compromise systems, applications, or networks. \n It is determined automatically by the discovery of exploits.",
18+
help_text="""Exploitability indicates the likelihood that a vulnerability in a software package could
19+
be used by malicious actors to compromise systems, applications, or networks.
20+
This metric is determined automatically based on the discovery of known exploits.""",
1921
max_digits=4,
2022
null=True,
2123
),
@@ -25,7 +27,7 @@ class Migration(migrations.Migration):
2527
name="weighted_severity",
2628
field=models.DecimalField(
2729
decimal_places=2,
28-
help_text="Weighted Severity is the maximum value obtained when each Severity is multiplied by its associated Weight/10.",
30+
help_text="Weighted severity is the highest value calculated by multiplying each severity by its corresponding weight, divided by 10.",
2931
max_digits=4,
3032
null=True,
3133
),

vulnerabilities/models.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -206,16 +206,16 @@ class Vulnerability(models.Model):
206206
null=True,
207207
max_digits=4,
208208
decimal_places=2,
209-
help_text="""Exploitability refers to the potential or probability of a software package vulnerability being
210-
exploited by malicious actors to compromise systems, applications, or networks.
211-
It is determined automatically by the discovery of exploits.""",
209+
help_text=""""Exploitability indicates the likelihood that a vulnerability in a software package could be used
210+
by malicious actors to compromise systems, applications, or networks.
211+
This metric is determined automatically based on the discovery of known exploits.""",
212212
)
213213

214214
weighted_severity = models.DecimalField(
215215
null=True,
216216
max_digits=4,
217217
decimal_places=2,
218-
help_text="Weighted Severity is the maximum value obtained when each Severity is multiplied by its associated Weight/10.",
218+
help_text="Weighted severity is the highest value calculated by multiplying each severity by its corresponding weight, divided by 10.",
219219
)
220220

221221
@property
@@ -228,8 +228,8 @@ def risk_score(self):
228228
Risk = min(weighted severity * exploitability, 10)
229229
"""
230230
if self.exploitability is not None and self.weighted_severity is not None:
231-
return f"{min(float(self.exploitability) * float(self.weighted_severity), 10.0):.2f}"
232-
return None
231+
risk_score = min(float(self.exploitability) * float(self.weighted_severity), 10.0)
232+
return f"{risk_score:.2f}".rstrip("0").rstrip(".")
233233

234234
objects = VulnerabilityQuerySet.as_manager()
235235

vulnerabilities/pipelines/compute_package_risk.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@
99

1010
from aboutcode.pipeline import LoopProgress
1111

12-
from vulnerabilities.models import AffectedByPackageRelatedVulnerability
1312
from vulnerabilities.models import Package
1413
from vulnerabilities.models import Vulnerability
1514
from vulnerabilities.pipelines import VulnerableCodePipeline
1615
from vulnerabilities.risk import compute_package_risk
17-
from vulnerabilities.risk import compute_vulnerability_risk
16+
from vulnerabilities.risk import compute_vulnerability_risk_factors
1817

1918

2019
class ComputePackageRiskPipeline(VulnerableCodePipeline):
@@ -29,11 +28,16 @@ class ComputePackageRiskPipeline(VulnerableCodePipeline):
2928

3029
@classmethod
3130
def steps(cls):
32-
return (cls.add_vulnerability_risk_score, cls.add_package_risk_score)
31+
return (
32+
cls.compute_and_store_vulnerability_risk_score,
33+
cls.compute_and_store_package_risk_score,
34+
)
3335

34-
def add_vulnerability_risk_score(self):
35-
affected_vulnerabilities = Vulnerability.objects.filter(
36-
affectedbypackagerelatedvulnerability__isnull=False
36+
def compute_and_store_vulnerability_risk_score(self):
37+
affected_vulnerabilities = (
38+
Vulnerability.objects.filter(affectedbypackagerelatedvulnerability__isnull=False)
39+
.prefetch_related("references")
40+
.only("references", "exploits")
3741
)
3842

3943
self.log(
@@ -47,11 +51,13 @@ def add_vulnerability_risk_score(self):
4751
batch_size = 5000
4852

4953
for vulnerability in progress.iter(affected_vulnerabilities.paginated()):
54+
references = vulnerability.references
55+
severities = vulnerability.severities.select_related("reference")
5056

51-
vulnerability = compute_vulnerability_risk(vulnerability)
52-
53-
if not vulnerability:
54-
continue
57+
(
58+
vulnerability.weighted_severity,
59+
vulnerability.exploitability,
60+
) = compute_vulnerability_risk_factors(references, severities, vulnerability.exploits)
5561

5662
updatables.append(vulnerability)
5763

@@ -68,7 +74,7 @@ def add_vulnerability_risk_score(self):
6874
f"Successfully added risk score for {updated_vulnerability_count:,d} vulnerability"
6975
)
7076

71-
def add_package_risk_score(self):
77+
def compute_and_store_package_risk_score(self):
7278
affected_packages = Package.objects.filter(
7379
affected_by_vulnerabilities__isnull=False
7480
).distinct()

vulnerabilities/risk.py

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
from urllib.parse import urlparse
1212

13+
from django.db.models import Prefetch
14+
1315
from vulnerabilities.models import AffectedByPackageRelatedVulnerability
1416
from vulnerabilities.models import Exploit
1517
from vulnerabilities.models import Package
@@ -27,6 +29,8 @@ def get_weighted_severity(severities):
2729
by its associated Weight/10.
2830
Example of Weighted Severity: max(7*(10/10), 8*(3/10), 6*(8/10)) = 7
2931
"""
32+
if not severities:
33+
return 0
3034

3135
score_map = {
3236
"low": 3,
@@ -90,33 +94,21 @@ def get_exploitability_level(exploits, references, severities):
9094
return exploit_level
9195

9296

93-
def compute_vulnerability_risk(vulnerability: Vulnerability):
97+
def compute_vulnerability_risk_factors(references, severities, exploits):
9498
"""
95-
Computes the risk score for a given vulnerability.
96-
97-
Risk is expressed as a number ranging from 0 to 10 and is calculated based on:
98-
- Weighted severity: a value derived from the associated severities of the vulnerability.
99-
- Exploitability: a measure of how easily the vulnerability can be exploited.
100-
101-
The risk score is computed as:
102-
Risk = min(weighted_severity * exploitability, 10)
99+
Compute weighted severity and exploitability for a vulnerability.
103100
104101
Args:
105-
vulnerability (Vulnerability): The vulnerability object to compute the risk for.
102+
references (list): References linked to the vulnerability.
103+
severities (list): Severity levels of the vulnerability.
104+
exploits (list): Exploit details for the vulnerability.
106105
107106
Returns:
108-
Vulnerability: The updated vulnerability object with computed risk-related attributes.
109-
110-
Notes:
111-
- If there are no associated references, severities, or exploits, the computation is skipped.
107+
tuple: (weighted_severity, exploitability).
112108
"""
113-
references = vulnerability.references
114-
severities = vulnerability.severities.select_related("reference")
115-
exploits = Exploit.objects.filter(vulnerability=vulnerability)
116-
if references.exists() or severities.exists() or exploits.exists():
117-
vulnerability.weighted_severity = get_weighted_severity(severities)
118-
vulnerability.exploitability = get_exploitability_level(exploits, references, severities)
119-
return vulnerability
109+
weighted_severity = get_weighted_severity(severities)
110+
exploitability = get_exploitability_level(exploits, references, severities)
111+
return weighted_severity, exploitability
120112

121113

122114
def compute_package_risk(package: Package):
@@ -126,9 +118,15 @@ def compute_package_risk(package: Package):
126118
"""
127119

128120
result = []
129-
for pkg_related_vul in AffectedByPackageRelatedVulnerability.objects.filter(
121+
affected_pkg_related_vul = AffectedByPackageRelatedVulnerability.objects.filter(
130122
package=package
131-
).prefetch_related("vulnerability"):
123+
).prefetch_related(
124+
Prefetch(
125+
"vulnerability",
126+
queryset=Vulnerability.objects.only("weighted_severity", "exploitability"),
127+
)
128+
)
129+
for pkg_related_vul in affected_pkg_related_vul:
132130
if risk := pkg_related_vul.vulnerability.risk_score:
133131
result.append(float(risk))
134132

vulnerabilities/templates/vulnerability_details.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@
124124

125125
<tr>
126126
<td class="two-col-left"
127-
data-tooltip="Exploitability refers to the potential or probability of a software package vulnerability being
128-
exploited by malicious actors to compromise systems, applications, or networks.
129-
It is determined automatically by the discovery of exploits.">
127+
data-tooltip="Exploitability indicates the likelihood that a vulnerability in a software package could be used
128+
by malicious actors to compromise systems, applications, or networks.
129+
This metric is determined automatically based on the discovery of known exploits.">
130130
Exploitability</td>
131131
<td class="two-col-right wrap-strings">
132132
{{ vulnerability.exploitability }}
@@ -135,7 +135,7 @@
135135

136136
<tr>
137137
<td class="two-col-left"
138-
data-tooltip="Weighted Severity is the maximum value obtained when each Severity is multiplied by its associated Weight/10."
138+
data-tooltip="Weighted severity is the highest value calculated by multiplying each severity by its corresponding weight, divided by 10."
139139
>Weighted Severity</td>
140140
<td class="two-col-right wrap-strings">
141141
{{ vulnerability.weighted_severity }}

vulnerabilities/tests/pipelines/test_compute_package_risk.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ def test_simple_risk_pipeline(vulnerability):
3131
improver.execute()
3232

3333
pkg = Package.objects.get(type="pypi", name="foo", version="2.3.0")
34-
assert f"{pkg.risk_score:.2f}" == "3.10"
34+
assert pkg.risk_score == Decimal("10")

vulnerabilities/tests/test_risk.py

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from vulnerabilities.models import VulnerabilityRelatedReference
1616
from vulnerabilities.models import VulnerabilitySeverity
1717
from vulnerabilities.models import Weakness
18-
from vulnerabilities.risk import compute_vulnerability_risk
18+
from vulnerabilities.risk import compute_vulnerability_risk_factors
1919
from vulnerabilities.risk import get_exploitability_level
2020
from vulnerabilities.risk import get_weighted_severity
2121
from vulnerabilities.severity_systems import CVSSV3
@@ -169,6 +169,44 @@ def test_get_weighted_severity(vulnerability):
169169

170170

171171
@pytest.mark.django_db
172-
def test_compute_vulnerability_risk(vulnerability):
173-
vulnerability = compute_vulnerability_risk(vulnerability)
174-
assert vulnerability.risk_score == str(3.11)
172+
def test_compute_vulnerability_risk_factors(vulnerability):
173+
assert compute_vulnerability_risk_factors(
174+
vulnerability.references, vulnerability.severities, vulnerability.exploits
175+
) == (6.210000000000001, 2)
176+
assert compute_vulnerability_risk_factors(
177+
vulnerability.references, vulnerability.severities, None
178+
) == (
179+
6.210000000000001,
180+
0.5,
181+
)
182+
assert compute_vulnerability_risk_factors(
183+
vulnerability.references, None, vulnerability.exploits
184+
) == (
185+
0,
186+
2,
187+
)
188+
assert compute_vulnerability_risk_factors(None, None, None) == (0, 0.5)
189+
190+
191+
@pytest.mark.django_db
192+
def test_get_vulnerability_risk_score(vulnerability):
193+
vulnerability.weighted_severity = 6.0
194+
vulnerability.exploitability = 2
195+
196+
assert vulnerability.risk_score == "10" # max risk_score can be reached
197+
198+
vulnerability.weighted_severity = 6
199+
vulnerability.exploitability = 0.5
200+
assert vulnerability.risk_score == "3"
201+
202+
vulnerability.weighted_severity = 5.6
203+
vulnerability.exploitability = 0.5
204+
assert vulnerability.risk_score == "2.8"
205+
206+
vulnerability.weighted_severity = None
207+
vulnerability.exploitability = 0.5
208+
assert vulnerability.risk_score is None
209+
210+
vulnerability.weighted_severity = None
211+
vulnerability.exploitability = None
212+
assert vulnerability.risk_score is None

0 commit comments

Comments
 (0)