-
Notifications
You must be signed in to change notification settings - Fork 218
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for Calculating Package Vulnerability Risk (#1593)
* Migrate ( metasploit, exploit-db, kev ) to aboutcode pipeline. Set data_source as the header for the exploit table. Squash the migration files into a single file. Add test for exploit-db , metasploit Add a missing migration file Rename resources_and_notes to notes Fix Api test Refactor metasploit , exploitdb , kev improver Rename Kev tab to exploit tab Add support for exploitdb , metasploit, kev Signed-off-by: ziadhany <ziadhany2016@gmail.com> Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Implement the appropriate LoopProgress progress bar. Refactor the error handling logic in the code. Signed-off-by: ziadhany <ziadhany2016@gmail.com> Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Fix migration conflict Add pipeline_id for ( kev, metasploit, exploit-db ) Signed-off-by: ziadhany <ziadhany2016@gmail.com> Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Migrate ( metasploit, exploit-db, kev ) to aboutcode pipeline. Set data_source as the header for the exploit table. Squash the migration files into a single file. Add test for exploit-db , metasploit Add a missing migration file Rename resources_and_notes to notes Fix Api test Refactor metasploit , exploitdb , kev improver Rename Kev tab to exploit tab Add support for exploitdb , metasploit, kev Signed-off-by: ziadhany <ziadhany2016@gmail.com> Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Implement the appropriate LoopProgress progress bar. Refactor the error handling logic in the code. Signed-off-by: ziadhany <ziadhany2016@gmail.com> Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Remove unwanted migration file Signed-off-by: ziadhany <ziadhany2016@gmail.com> Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Add support for Calculating Risk in VulnerableCode Signed-off-by: ziadhany <ziadhany2016@gmail.com> Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Remove unwanted migration file Signed-off-by: ziadhany <ziadhany2016@gmail.com> Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Add a prefetch to try to optimize query performance Signed-off-by: ziadhany <ziadhany2016@gmail.com> Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Empty risk when there is no data Signed-off-by: ziadhany <ziadhany2016@gmail.com> Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Create a pipeline for package risk Signed-off-by: ziadhany <ziadhany2016@gmail.com> Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Load the weight once uncomment all importers Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Update the risk description in the model. Rename the pipeline from RiskPackagePipeline to ComputePackageRiskPipeline. Add a tooltip for risk, and remove any unused imports in the view. Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Rename the pipeline step from add_risk_package to add_package_risk_score and remove any extra whitespace in views.py. Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Resolve migration conflict Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Rename the pipeline file Add pagination and refactor bulk_update_package Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Update the weight_config dict and modify it to use domain names. Signed-off-by: ziad hany <ziadhany2016@gmail.com> * Add license header Signed-off-by: Keshav Priyadarshi <git@keshav.space> * Move weight config to python file Signed-off-by: Keshav Priyadarshi <git@keshav.space> * Skip packages with no risk score Signed-off-by: Keshav Priyadarshi <git@keshav.space> --------- Signed-off-by: ziad hany <ziadhany2016@gmail.com> Signed-off-by: Keshav Priyadarshi <git@keshav.space> Co-authored-by: Keshav Priyadarshi <git@keshav.space>
- Loading branch information
1 parent
38b97ff
commit 0e13b55
Showing
18 changed files
with
3,429 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Generated by Django 4.2.16 on 2024-10-29 10:55 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
("vulnerabilities", "0074_update_pysec_advisory_created_by"), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name="package", | ||
name="risk_score", | ||
field=models.DecimalField( | ||
decimal_places=2, | ||
help_text="Risk score between 0.00 and 10.00, where higher values indicate greater vulnerability risk for the package.", | ||
max_digits=4, | ||
null=True, | ||
), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# | ||
# Copyright (c) nexB Inc. and others. All rights reserved. | ||
# VulnerableCode is a trademark of nexB Inc. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. | ||
# See https://github.com/aboutcode-org/vulnerablecode for support or download. | ||
# See https://aboutcode.org for more information about nexB OSS projects. | ||
# | ||
|
||
from aboutcode.pipeline import LoopProgress | ||
|
||
from vulnerabilities.models import Package | ||
from vulnerabilities.pipelines import VulnerableCodePipeline | ||
from vulnerabilities.risk import compute_package_risk | ||
|
||
|
||
class ComputePackageRiskPipeline(VulnerableCodePipeline): | ||
""" | ||
Compute risk score for packages. | ||
See https://github.com/aboutcode-org/vulnerablecode/issues/1543 | ||
""" | ||
|
||
pipeline_id = "compute_package_risk" | ||
license_expression = None | ||
|
||
@classmethod | ||
def steps(cls): | ||
return (cls.add_package_risk_score,) | ||
|
||
def add_package_risk_score(self): | ||
affected_packages = Package.objects.filter( | ||
affected_by_vulnerabilities__isnull=False | ||
).distinct() | ||
|
||
self.log(f"Calculating risk for {affected_packages.count():,d} affected package records") | ||
|
||
progress = LoopProgress(total_iterations=affected_packages.count(), logger=self.log) | ||
|
||
updatables = [] | ||
updated_package_count = 0 | ||
batch_size = 5000 | ||
|
||
for package in progress.iter(affected_packages.paginated()): | ||
risk_score = compute_package_risk(package) | ||
|
||
if not risk_score: | ||
continue | ||
|
||
package.risk_score = risk_score | ||
updatables.append(package) | ||
|
||
if len(updatables) >= batch_size: | ||
updated_package_count += bulk_update_package_risk_score( | ||
packages=updatables, | ||
logger=self.log, | ||
) | ||
updated_package_count += bulk_update_package_risk_score( | ||
packages=updatables, | ||
logger=self.log, | ||
) | ||
self.log(f"Successfully added risk score for {updated_package_count:,d} package") | ||
|
||
|
||
def bulk_update_package_risk_score(packages, logger): | ||
package_count = 0 | ||
if packages: | ||
try: | ||
Package.objects.bulk_update(objs=packages, fields=["risk_score"]) | ||
package_count += len(packages) | ||
except Exception as e: | ||
logger(f"Error updating packages: {e}") | ||
packages.clear() | ||
return package_count |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
# | ||
# Copyright (c) nexB Inc. and others. All rights reserved. | ||
# VulnerableCode is a trademark of nexB Inc. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. | ||
# See https://github.com/aboutcode-org/vulnerablecode for support or download. | ||
# See https://aboutcode.org for more information about nexB OSS projects. | ||
# | ||
|
||
|
||
from urllib.parse import urlparse | ||
|
||
from vulnerabilities.models import AffectedByPackageRelatedVulnerability | ||
from vulnerabilities.models import Exploit | ||
from vulnerabilities.models import Package | ||
from vulnerabilities.models import Vulnerability | ||
from vulnerabilities.models import VulnerabilityReference | ||
from vulnerabilities.severity_systems import EPSS | ||
from vulnerabilities.weight_config import WEIGHT_CONFIG | ||
|
||
DEFAULT_WEIGHT = 5 | ||
|
||
|
||
def get_weighted_severity(severities): | ||
""" | ||
Weighted Severity is the maximum value obtained when each Severity is multiplied | ||
by its associated Weight/10. | ||
Example of Weighted Severity: max(7*(10/10), 8*(3/10), 6*(8/10)) = 7 | ||
""" | ||
|
||
score_map = { | ||
"low": 3, | ||
"moderate": 6.9, | ||
"medium": 6.9, | ||
"high": 8.9, | ||
"important": 8.9, | ||
"critical": 10.0, | ||
"urgent": 10.0, | ||
} | ||
|
||
score_list = [] | ||
for severity in severities: | ||
parsed_url = urlparse(severity.reference.url) | ||
severity_source = parsed_url.netloc.replace("www.", "", 1) | ||
weight = WEIGHT_CONFIG.get(severity_source, DEFAULT_WEIGHT) | ||
max_weight = float(weight) / 10 | ||
vul_score = severity.value | ||
try: | ||
vul_score = float(vul_score) | ||
vul_score_value = vul_score * max_weight | ||
except ValueError: | ||
vul_score = vul_score.lower() | ||
vul_score_value = score_map.get(vul_score, 0) * max_weight | ||
|
||
score_list.append(vul_score_value) | ||
return max(score_list) if score_list else 0 | ||
|
||
|
||
def get_exploitability_level(exploits, references, severities): | ||
""" | ||
Exploitability refers to the potential or | ||
probability of a software package vulnerability being exploited by | ||
malicious actors to compromise systems, applications, or networks. | ||
It is determined automatically by discovery of exploits. | ||
""" | ||
# no exploit known ( default .5) | ||
exploit_level = 0.5 | ||
|
||
if exploits: | ||
# Automatable Exploit with PoC script published OR known exploits (KEV) in the wild OR known ransomware | ||
exploit_level = 2 | ||
|
||
elif severities: | ||
# high EPSS. | ||
epss = severities.filter( | ||
scoring_system=EPSS.identifier, | ||
) | ||
epss = any(float(epss.value) > 0.8 for epss in epss) | ||
if epss: | ||
exploit_level = 2 | ||
|
||
elif references: | ||
# PoC/Exploit script published | ||
ref_exploits = references.filter( | ||
reference_type=VulnerabilityReference.EXPLOIT, | ||
) | ||
if ref_exploits: | ||
exploit_level = 1 | ||
|
||
return exploit_level | ||
|
||
|
||
def compute_vulnerability_risk(vulnerability: Vulnerability): | ||
""" | ||
Risk may be expressed as a number ranging from 0 to 10. | ||
Risk is calculated from weighted severity and exploitability values. | ||
It is the maximum value of (the weighted severity multiplied by its exploitability) or 10 | ||
Risk = min(weighted severity * exploitability, 10) | ||
""" | ||
references = vulnerability.references | ||
severities = vulnerability.severities.select_related("reference") | ||
exploits = Exploit.objects.filter(vulnerability=vulnerability) | ||
if references.exists() or severities.exists() or exploits.exists(): | ||
weighted_severity = get_weighted_severity(severities) | ||
exploitability = get_exploitability_level(exploits, references, severities) | ||
return min(weighted_severity * exploitability, 10) | ||
|
||
|
||
def compute_package_risk(package: Package): | ||
""" | ||
Calculate the risk for a package by iterating over all vulnerabilities that affects this package | ||
and determining the associated risk. | ||
""" | ||
|
||
result = [] | ||
for pkg_related_vul in AffectedByPackageRelatedVulnerability.objects.filter( | ||
package=package | ||
).prefetch_related("vulnerability"): | ||
if risk := compute_vulnerability_risk(pkg_related_vul.vulnerability): | ||
result.append(risk) | ||
|
||
if not result: | ||
return | ||
|
||
return f"{max(result):.2f}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
vulnerabilities/tests/pipelines/test_compute_package_risk.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# | ||
# Copyright (c) nexB Inc. and others. All rights reserved. | ||
# VulnerableCode is a trademark of nexB Inc. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. | ||
# See https://github.com/aboutcode-org/vulnerablecode for support or download. | ||
# See https://aboutcode.org for more information about nexB OSS projects. | ||
# | ||
|
||
import pytest | ||
|
||
from vulnerabilities.models import AffectedByPackageRelatedVulnerability | ||
from vulnerabilities.models import Package | ||
from vulnerabilities.pipelines.compute_package_risk import ComputePackageRiskPipeline | ||
from vulnerabilities.tests.test_risk import vulnerability | ||
|
||
|
||
@pytest.mark.django_db | ||
def test_simple_risk_pipeline(vulnerability): | ||
pkg = Package.objects.create(type="pypi", name="foo", version="2.3.0") | ||
assert Package.objects.count() == 1 | ||
|
||
improver = ComputePackageRiskPipeline() | ||
improver.execute() | ||
|
||
assert pkg.risk_score is None | ||
|
||
AffectedByPackageRelatedVulnerability.objects.create(package=pkg, vulnerability=vulnerability) | ||
improver = ComputePackageRiskPipeline() | ||
improver.execute() | ||
|
||
pkg = Package.objects.get(type="pypi", name="foo", version="2.3.0") | ||
assert str(pkg.risk_score) == str(3.11) |
9 changes: 9 additions & 0 deletions
9
...ilities/tests/pipelines/test_exploitdb.py → .../pipelines/test_enhance_with_exploitdb.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
vulnerabilities/tests/pipelines/test_kev.py → .../tests/pipelines/test_enhance_with_kev.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
...lities/tests/pipelines/test_metasploit.py → ...pipelines/test_enhance_with_metasploit.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.