Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not report ghost package as a fix for vulnerability #1679

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
14 changes: 11 additions & 3 deletions vulnerabilities/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.throttling import AnonRateThrottle
from rest_framework.throttling import UserRateThrottle

from vulnerabilities.models import Alias
from vulnerabilities.models import Exploit
Expand Down Expand Up @@ -369,6 +367,10 @@ def get_fixing_vulnerabilities(self, package) -> dict:
"""
Return a mapping of vulnerabilities fixed in the given `package`.
"""
# Ghost package should not fix any vulnerability.
if package.is_ghost:
return []

return self.get_vulnerabilities_for_a_package(package=package, fix=True)

def get_affected_vulnerabilities(self, package) -> dict:
Expand Down Expand Up @@ -643,7 +645,10 @@ def get_fixed_packages_qs(self):
"""
return (
self.get_packages_qs()
.filter(fixingpackagerelatedvulnerability__isnull=False)
.filter(
fixingpackagerelatedvulnerability__isnull=False,
is_ghost=False,
)
.with_is_vulnerable()
)

Expand All @@ -669,6 +674,9 @@ def get_queryset(self):
.get_queryset()
.prefetch_related(
"weaknesses",
"references",
"exploits",
"severities",
Prefetch(
"fixed_by_packages",
queryset=self.get_fixed_packages_qs(),
Expand Down
3 changes: 3 additions & 0 deletions vulnerabilities/api_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ def get_affected_by_vulnerabilities(self, obj):
return [vuln.vulnerability_id for vuln in obj.affected_by_vulnerabilities.all()]

def get_fixing_vulnerabilities(self, obj):
# Ghost package should not fix any vulnerability.
if obj.is_ghost:
return []
return [vuln.vulnerability_id for vuln in obj.fixing_vulnerabilities.all()]


Expand Down
2 changes: 1 addition & 1 deletion vulnerabilities/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ def only_vulnerable(self):
return self._vulnerable(True)

def only_non_vulnerable(self):
return self._vulnerable(False)
return self._vulnerable(False).filter(is_ghost=False)

def _vulnerable(self, vulnerable=True):
"""
Expand Down
221 changes: 219 additions & 2 deletions vulnerabilities/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

import json
import os
from collections import OrderedDict
from urllib.parse import quote

from django.test import TestCase
Expand All @@ -31,7 +30,6 @@
from vulnerabilities.models import VulnerabilitySeverity
from vulnerabilities.models import Weakness
from vulnerabilities.severity_systems import EPSS
from vulnerabilities.tests import util_tests

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
TEST_DATA = os.path.join(BASE_DIR, "test_data")
Expand Down Expand Up @@ -355,6 +353,55 @@ def test_api_with_single_vulnerability_with_filters(self):
"weighted_severity": None,
}

def test_api_with_single_vulnerability_no_ghost_fix(self):
self.pkg2.is_ghost = True
self.pkg1.is_ghost = True
self.pkg2.save()
self.pkg1.save()

response = self.csrf_client.get(
f"/api/vulnerabilities/{self.vulnerability.id}", format="json"
).data

expected = {
"url": f"http://testserver/api/vulnerabilities/{self.vulnerability.id}",
"vulnerability_id": self.vulnerability.vulnerability_id,
"summary": "test",
"severity_range_score": None,
"aliases": [],
"resource_url": f"http://testserver/vulnerabilities/{self.vulnerability.vulnerability_id}",
"fixed_packages": [],
"affected_packages": [],
"references": [
{
"reference_url": "https://.com",
"reference_id": "",
"reference_type": "",
"scores": [
{
"value": "0.526",
"scoring_system": "epss",
"scoring_elements": ".0016",
}
],
"url": "https://.com",
}
],
"weaknesses": [
{
"cwe_id": 119,
"name": "Improper Restriction of Operations within the Bounds of a Memory Buffer",
"description": "The product performs operations on a memory buffer, but it can read from or write to a memory location that is outside of the intended boundary of the buffer.",
},
],
"exploits": [],
"risk_score": None,
"exploitability": None,
"weighted_severity": None,
}

assert expected == response


def set_as_affected_by(package, vulnerability):
"""
Expand Down Expand Up @@ -743,6 +790,176 @@ def test_api_with_ignorning_qualifiers(self):
== "pkg:maven/com.fasterxml.jackson.core/[email protected]"
)

def test_api_with_ghost_package_no_fixing_vulnerabilities(self):
self.pkg_2_13_1.is_ghost = True
self.pkg_2_13_1.save()

response = self.csrf_client.get(f"/api/packages/{self.pkg_2_13_1.id}", format="json").data

expected = {
"url": "http://testserver/api/packages/{0}".format(self.pkg_2_13_1.id),
"purl": "pkg:maven/com.fasterxml.jackson.core/[email protected]",
"type": "maven",
"namespace": "com.fasterxml.jackson.core",
"name": "jackson-databind",
"version": "2.13.1",
"qualifiers": {},
"subpath": "",
"is_vulnerable": True,
"next_non_vulnerable_version": "2.14.0-rc1",
"latest_non_vulnerable_version": "2.14.0-rc1",
"affected_by_vulnerabilities": [
{
"url": "http://testserver/api/vulnerabilities/{0}".format(self.vul1.id),
"vulnerability_id": "VCID-vul1-vul1-vul1",
"summary": "This is VCID-vul1-vul1-vul1",
"references": [
{
"reference_url": "https://example.com",
"reference_id": "CVE-xxx-xxx",
"reference_type": "advisory",
"scores": [
{
"value": "0.526",
"scoring_system": "epss",
"scoring_elements": ".0016",
}
],
"url": "https://example.com",
}
],
"fixed_packages": [
{
"url": "http://testserver/api/packages/{0}".format(self.pkg_2_13_2.id),
"purl": "pkg:maven/com.fasterxml.jackson.core/[email protected]",
"is_vulnerable": True,
"affected_by_vulnerabilities": [
{"vulnerability": "VCID-vul2-vul2-vul2"}
],
"resource_url": "http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/[email protected]",
}
],
"aliases": ["CVE-2020-36518", "GHSA-57j2-w4cx-62h2"],
"risk_score": None,
"exploitability": None,
"weighted_severity": None,
"resource_url": "http://testserver/vulnerabilities/VCID-vul1-vul1-vul1",
}
],
"fixing_vulnerabilities": [],
"risk_score": None,
"resource_url": "http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/[email protected]",
}

assert response == expected

def test_api_with_ghost_package_no_next_latest_non_vulnerabilities(self):
self.pkg_2_14_0_rc1.is_ghost = True
self.pkg_2_14_0_rc1.save()

response = self.csrf_client.get(f"/api/packages/{self.pkg_2_13_1.id}", format="json").data

expected = {
"url": "http://testserver/api/packages/{0}".format(self.pkg_2_13_1.id),
"purl": "pkg:maven/com.fasterxml.jackson.core/[email protected]",
"type": "maven",
"namespace": "com.fasterxml.jackson.core",
"name": "jackson-databind",
"version": "2.13.1",
"qualifiers": {},
"subpath": "",
"is_vulnerable": True,
"next_non_vulnerable_version": None,
"latest_non_vulnerable_version": None,
"affected_by_vulnerabilities": [
{
"url": "http://testserver/api/vulnerabilities/{0}".format(self.vul1.id),
"vulnerability_id": "VCID-vul1-vul1-vul1",
"summary": "This is VCID-vul1-vul1-vul1",
"references": [
{
"reference_url": "https://example.com",
"reference_id": "CVE-xxx-xxx",
"reference_type": "advisory",
"scores": [
{
"value": "0.526",
"scoring_system": "epss",
"scoring_elements": ".0016",
}
],
"url": "https://example.com",
}
],
"fixed_packages": [
{
"url": "http://testserver/api/packages/{0}".format(self.pkg_2_13_2.id),
"purl": "pkg:maven/com.fasterxml.jackson.core/[email protected]",
"is_vulnerable": True,
"affected_by_vulnerabilities": [
{"vulnerability": "VCID-vul2-vul2-vul2"}
],
"resource_url": "http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/[email protected]",
}
],
"aliases": ["CVE-2020-36518", "GHSA-57j2-w4cx-62h2"],
"risk_score": None,
"exploitability": None,
"weighted_severity": None,
"resource_url": "http://testserver/vulnerabilities/VCID-vul1-vul1-vul1",
}
],
"fixing_vulnerabilities": [
{
"url": "http://testserver/api/vulnerabilities/{0}".format(self.vul3.id),
"vulnerability_id": "VCID-vul3-vul3-vul3",
"summary": "This is VCID-vul3-vul3-vul3",
"references": [
{
"reference_url": "https://example.com",
"reference_id": "CVE-xxx-xxx",
"reference_type": "advisory",
"scores": [
{
"value": "0.526",
"scoring_system": "epss",
"scoring_elements": ".0016",
}
],
"url": "https://example.com",
}
],
"fixed_packages": [
{
"url": "http://testserver/api/packages/{0}".format(self.pkg_2_12_6.id),
"purl": "pkg:maven/com.fasterxml.jackson.core/[email protected]",
"is_vulnerable": False,
"affected_by_vulnerabilities": [],
"resource_url": "http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/[email protected]",
},
{
"url": "http://testserver/api/packages/{0}".format(self.pkg_2_13_1.id),
"purl": "pkg:maven/com.fasterxml.jackson.core/[email protected]",
"is_vulnerable": True,
"affected_by_vulnerabilities": [
{"vulnerability": "VCID-vul1-vul1-vul1"}
],
"resource_url": "http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/[email protected]",
},
],
"aliases": ["CVE-2021-46877", "GHSA-3x8x-79m2-3w2w"],
"risk_score": None,
"exploitability": None,
"weighted_severity": None,
"resource_url": "http://testserver/vulnerabilities/VCID-vul3-vul3-vul3",
}
],
"risk_score": None,
"resource_url": "http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/[email protected]",
}

assert response == expected


class CPEApi(TestCase):
def setUp(self):
Expand Down
22 changes: 20 additions & 2 deletions vulnerabilities/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@ def get_context_data(self, **kwargs):
package = self.object
context["package"] = package
context["affected_by_vulnerabilities"] = package.affected_by.order_by("vulnerability_id")
context["fixing_vulnerabilities"] = package.fixing.order_by("vulnerability_id")
# Ghost package should not fix any vulnerability.
context["fixing_vulnerabilities"] = (
None if package.is_ghost else package.fixing.order_by("vulnerability_id")
)
context["package_search_form"] = PackageSearchForm(self.request.GET)
context["fixed_package_details"] = package.fixed_package_details

Expand Down Expand Up @@ -153,7 +156,17 @@ class VulnerabilityDetails(DetailView):
slug_field = "vulnerability_id"

def get_queryset(self):
return super().get_queryset().prefetch_related("references", "aliases", "weaknesses")
return (
super()
.get_queryset()
.prefetch_related(
"references",
"aliases",
"weaknesses",
"severities",
"exploits",
)
)

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
Expand Down Expand Up @@ -193,6 +206,11 @@ def get_context_data(self, **kwargs):
affected_fixed_by_matches["affected_package"] = sorted_affected_package
matched_fixed_by_packages = []
for fixed_by_package in sorted_fixed_by_packages:

# Ghost Package can't fix vulnerability.
if fixed_by_package.is_ghost:
continue

sorted_affected_version_class = get_purl_version_class(sorted_affected_package)
fixed_by_version_class = get_purl_version_class(fixed_by_package)
if (
Expand Down
Loading