Skip to content

Commit 35eb0a4

Browse files
committed
Add support for Calculating Risk in VulnerableCode
Signed-off-by: ziadhany <[email protected]>
1 parent 0446fff commit 35eb0a4

File tree

4 files changed

+221
-0
lines changed

4 files changed

+221
-0
lines changed

vulnerabilities/improvers/valid_versions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from vulnerabilities.importers.debian import DebianImporter
3232
from vulnerabilities.importers.debian_oval import DebianOvalImporter
3333
from vulnerabilities.importers.elixir_security import ElixirSecurityImporter
34+
from vulnerabilities.importers.epss import EPSSImporter
3435
from vulnerabilities.importers.github import GitHubAPIImporter
3536
from vulnerabilities.importers.github_osv import GithubOSVImporter
3637
from vulnerabilities.importers.gitlab import GitLabAPIImporter
@@ -472,3 +473,8 @@ class RubyImprover(ValidVersionImprover):
472473
class GithubOSVImprover(ValidVersionImprover):
473474
importer = GithubOSVImporter
474475
ignorable_versions = []
476+
477+
478+
class EPSSImprover(ValidVersionImprover):
479+
importer = EPSSImporter
480+
ignorable_versions = []

vulnerabilities/risk.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import os
2+
from typing import List
3+
4+
from vulnerabilities.models import Exploit
5+
from vulnerabilities.models import Package
6+
from vulnerabilities.models import PackageRelatedVulnerability
7+
from vulnerabilities.models import Vulnerability
8+
from vulnerabilities.models import VulnerabilityReference
9+
from vulnerabilities.models import VulnerabilityRelatedReference
10+
from vulnerabilities.models import VulnerabilitySeverity
11+
from vulnerabilities.utils import load_json
12+
13+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
14+
15+
16+
def get_weighted_severity(references: List[VulnerabilityReference]):
17+
"""
18+
Weighted Severity is the maximum value obtained when each Severity is multiplied
19+
by its associated Weight/10.
20+
Example of Weighted Severity: max(7*(10/10), 8*(3/10), 6*(8/10)) = 7
21+
"""
22+
weight_config_path = os.path.join(BASE_DIR, "..", "weight_config.json")
23+
weight_config = load_json(weight_config_path)
24+
25+
score_map = {
26+
"low": 3,
27+
"moderate": 6.9,
28+
"medium": 6.9,
29+
"high": 8.9,
30+
"important": 8.9,
31+
"critical": 10.0,
32+
"urgent": 10.0,
33+
}
34+
35+
score_list = []
36+
for reference in references:
37+
weight = weight_config.get(reference.url) # TODO use regular expression
38+
vul_severities = VulnerabilitySeverity.objects.filter(reference=reference)
39+
for vul_severity in vul_severities:
40+
vul_score = vul_severity.value
41+
42+
try:
43+
vul_score = float(vul_score)
44+
vul_score_value = vul_score * weight
45+
except ValueError:
46+
vul_score = vul_score.lower()
47+
vul_score_value = score_map.get(vul_score, 0) * weight
48+
49+
score_list.append(vul_score_value)
50+
return max(score_list)
51+
52+
53+
def get_exploitability_level(vulnerability: Vulnerability):
54+
"""
55+
Exploitability refers to the potential or
56+
probability of a software package vulnerability being exploited by
57+
malicious actors to compromise systems, applications, or networks.
58+
It is determined automatically by discovery of exploits.
59+
"""
60+
# no exploit known ( default )
61+
exploit_level = 0.5
62+
63+
exploits = Exploit.objects.filter(vulnerability=vulnerability)
64+
65+
ref = VulnerabilityRelatedReference.objects.filter(
66+
reference__reference_type=VulnerabilityReference.EXPLOIT, vulnerability=vulnerability
67+
)
68+
69+
if exploits:
70+
# Automatable Exploit with PoC script published OR known exploits (KEV) in the wild OR known ransomware OR high EPSS.
71+
72+
# TODO: Fix EPSS
73+
exploit_level = 2
74+
75+
elif ref:
76+
# PoC/Exploit script published
77+
exploit_level = 1
78+
79+
return exploit_level
80+
81+
82+
def calculate_vulnerability_risk(vulnerability: Vulnerability):
83+
"""
84+
Risk may be expressed as a number ranging from 0 to 10.
85+
Risk is calculated from weighted severity and exploitability values.
86+
It is the maximum value of (the weighted severity multiplied by its exploitability) or 10
87+
88+
Risk = min(weighted severity * exploitability, 10)
89+
"""
90+
refs = vulnerability.references.all()
91+
weighted_severity = get_weighted_severity(refs)
92+
exploitability = get_exploitability_level(vulnerability)
93+
return min(weighted_severity * exploitability, 10)
94+
95+
96+
def calculate_pkg_risk(package: Package):
97+
""" """
98+
result = []
99+
for pkg_related_vul in PackageRelatedVulnerability.objects.filter(package=package, fix=False):
100+
risk = calculate_vulnerability_risk(pkg_related_vul.val)
101+
result.append(risk)
102+
return result

vulnerabilities/tests/test_risk.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import pytest
2+
3+
from vulnerabilities.models import Exploit
4+
from vulnerabilities.models import Vulnerability
5+
from vulnerabilities.models import VulnerabilityReference
6+
from vulnerabilities.models import VulnerabilityRelatedReference
7+
from vulnerabilities.models import VulnerabilitySeverity
8+
from vulnerabilities.models import Weakness
9+
from vulnerabilities.risk import calculate_vulnerability_risk
10+
from vulnerabilities.risk import get_exploitability_level
11+
from vulnerabilities.risk import get_weighted_severity
12+
from vulnerabilities.severity_systems import CVSSV3
13+
from vulnerabilities.severity_systems import GENERIC
14+
15+
16+
@pytest.fixture
17+
@pytest.mark.django_db
18+
def vulnerability():
19+
vul = Vulnerability(vulnerability_id="VCID-Existing")
20+
vul.save()
21+
22+
reference1 = VulnerabilityReference.objects.create(
23+
reference_id="",
24+
url="https://nvd.nist.gov/*",
25+
)
26+
27+
VulnerabilitySeverity.objects.create(
28+
reference=reference1,
29+
scoring_system=CVSSV3.identifier,
30+
scoring_elements="CVSS:3.0/AV:P/AC:H/PR:H/UI:R/S:C/C:H/I:H/A:N/E:H/RL:O/RC:R/CR:H/MAC:H/MC:L",
31+
value="6.5",
32+
)
33+
34+
VulnerabilitySeverity.objects.create(
35+
reference=reference1,
36+
scoring_system=GENERIC.identifier,
37+
value="MODERATE", # 6.9
38+
)
39+
40+
VulnerabilityRelatedReference.objects.create(reference=reference1, vulnerability=vul)
41+
42+
weaknesses = Weakness.objects.create(cwe_id=119)
43+
vul.weaknesses.add(weaknesses)
44+
return vul
45+
46+
47+
@pytest.mark.django_db
48+
def test_get_weighted_severity(vulnerability):
49+
refs = vulnerability.references.all()
50+
assert get_weighted_severity(refs) == 4.83 # max([6.5*.7, 6.9*.7])
51+
52+
reference2 = VulnerabilityReference.objects.create(
53+
reference_id="",
54+
url="https://www.debian.org/security/*",
55+
)
56+
57+
VulnerabilitySeverity.objects.create(
58+
reference=reference2,
59+
scoring_system=GENERIC.identifier,
60+
value="CRITICAL",
61+
)
62+
63+
VulnerabilityRelatedReference.objects.create(reference=reference2, vulnerability=vulnerability)
64+
65+
refs = vulnerability.references.all()
66+
assert get_weighted_severity(refs) == 8.0
67+
68+
69+
@pytest.fixture
70+
@pytest.mark.django_db
71+
def vulnerability_with_exploit():
72+
vul = Vulnerability(vulnerability_id="VCID-Exploit")
73+
vul.save()
74+
75+
Exploit.objects.create(vulnerability=vul, description="exploit description")
76+
return vul
77+
78+
79+
@pytest.fixture
80+
@pytest.mark.django_db
81+
def vulnerability_with_exploit_ref():
82+
vul = Vulnerability(vulnerability_id="VCID-Exploit-Ref")
83+
vul.save()
84+
85+
reference_exploit = VulnerabilityReference.objects.create(
86+
reference_id="",
87+
reference_type=VulnerabilityReference.EXPLOIT,
88+
url="https://...",
89+
)
90+
91+
VulnerabilityRelatedReference.objects.create(reference=reference_exploit, vulnerability=vul)
92+
return vul
93+
94+
95+
@pytest.mark.django_db
96+
def test_exploitability_level(
97+
vulnerability_with_exploit, vulnerability_with_exploit_ref, vulnerability
98+
):
99+
assert get_exploitability_level(vulnerability_with_exploit) == 2
100+
101+
assert get_exploitability_level(vulnerability_with_exploit_ref) == 1
102+
103+
assert get_exploitability_level(vulnerability) == 0.5
104+
105+
106+
@pytest.mark.django_db
107+
def test_calculate_vulnerability_risk(vulnerability):
108+
assert calculate_vulnerability_risk(vulnerability) == 2.415

weight_config.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"https://nvd.nist.gov/*": 0.7,
3+
"https://www.debian.org/security/*": 0.8,
4+
"https://github.com/nexB/vulnerablecode/*": 1
5+
}

0 commit comments

Comments
 (0)