Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions vulnerabilities/pipelines/v2_importers/gitlab_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import pytz
import saneyaml
from cvss.exceptions import CVSS2MalformedError
from cvss.exceptions import CVSS3MalformedError
from dateutil import parser as dateparser
from fetchcode.vcs import fetch_via_vcs
from packageurl import PackageURL
Expand Down Expand Up @@ -302,23 +304,41 @@ def parse_gitlab_advisory(
cvss_v3 = gitlab_advisory.get("cvss_v3")
severities = []
if cvss_v2:
cvss_v2_clean = cvss_v2.strip()
value = None
try:
value = SCORING_SYSTEMS["cvssv2"].compute(cvss_v2_clean)
except CVSS2MalformedError as e:
logger(
f"parse_gitlab_advisory: Invalid CVSSv2 vector {cvss_v2!r}: {e}",
level=logging.ERROR,
)
severities.append(
VulnerabilitySeverity(
system=SCORING_SYSTEMS["cvssv2"],
scoring_elements=cvss_v2,
value=None,
value=value,
url=advisory_url,
)
)
if cvss_v3:
cvss_v3_clean = cvss_v3.strip()
scoring_system = SCORING_SYSTEMS["cvssv3"]
if cvss_v3.startswith("CVSS:3.1/"):
if cvss_v3_clean.startswith("CVSS:3.1/"):
scoring_system = SCORING_SYSTEMS["cvssv3.1"]
value = None
try:
value = scoring_system.compute(cvss_v3_clean)
except CVSS3MalformedError as e:
logger(
f"parse_gitlab_advisory: Invalid CVSSv3 vector {cvss_v3!r}: {e}",
level=logging.ERROR,
)
severities.append(
VulnerabilitySeverity(
system=scoring_system,
scoring_elements=cvss_v3,
value=None,
value=value,
url=advisory_url,
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,125 @@ def mock_get_purl(package_slug, purl_type_by_gitlab_scheme, logger):
assert isinstance(result.date_published, datetime)
assert result.date_published.year == 2018
assert result.affected_packages == [] # Because get_purl was mocked to return None


def test_parse_gitlab_advisory_computes_cvss_scores(tmp_path):
content = {
"identifier": "CVE-2019-1010083",
"package_slug": "pypi/Flask",
"title": "Denial of service",
"description": "Denial of Service due to unexpected memory usage in the Pallets Project Flask",
"pubdate": "2019-07-17",
"affected_range": "<1.0",
"fixed_versions": ["1.0"],
"urls": ["https://nvd.nist.gov/vuln/detail/CVE-2019-1010083"],
"cvss_v2": "AV:N/AC:L/Au:N/C:N/I:N/A:P",
"cvss_v3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"cwe_ids": ["CWE-1035", "CWE-937"],
"identifiers": ["CVE-2019-1010083"],
}
advisory_path = tmp_path / "CVE-2019-1010083.yaml"
advisory_path.write_text(saneyaml.dump(content))

dummy_logger = lambda *args, **kwargs: None
result = parse_gitlab_advisory(
file=advisory_path,
base_path=advisory_path.parent,
gitlab_scheme_by_purl_type={"pypi": "pypi"},
purl_type_by_gitlab_scheme={"pypi": "pypi"},
logger=dummy_logger,
)

assert isinstance(result, AdvisoryDataV2)
assert len(result.severities) == 2

cvss_v2_sev = result.severities[0]
assert cvss_v2_sev.system.identifier == "cvssv2"
assert cvss_v2_sev.scoring_elements == "AV:N/AC:L/Au:N/C:N/I:N/A:P"
assert cvss_v2_sev.value == "5.0"

cvss_v3_sev = result.severities[1]
assert cvss_v3_sev.system.identifier == "cvssv3"
assert cvss_v3_sev.scoring_elements == "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
assert cvss_v3_sev.value == "7.5"


def test_parse_gitlab_advisory_cvss_v31_scoring_system(tmp_path):
content = {
"identifier": "CVE-2023-0001",
"package_slug": "pypi/django",
"title": "Vulnerability with CVSS 3.1",
"description": "Test CVSS 3.1 scoring system selection and computation",
"pubdate": "2023-01-01",
"affected_range": "<4.0.0",
"fixed_versions": ["4.0.0"],
"urls": ["https://example.com/advisory"],
"cvss_v3": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"cwe_ids": ["CWE-79"],
"identifiers": ["CVE-2023-0001"],
}
advisory_path = tmp_path / "CVE-2023-0001.yaml"
advisory_path.write_text(saneyaml.dump(content))

dummy_logger = lambda *args, **kwargs: None
result = parse_gitlab_advisory(
file=advisory_path,
base_path=advisory_path.parent,
gitlab_scheme_by_purl_type={"pypi": "pypi"},
purl_type_by_gitlab_scheme={"pypi": "pypi"},
logger=dummy_logger,
)

assert isinstance(result, AdvisoryDataV2)
assert len(result.severities) == 1
cvss_v31_sev = result.severities[0]
assert cvss_v31_sev.system.identifier == "cvssv3.1"
assert cvss_v31_sev.scoring_elements == "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
assert cvss_v31_sev.value == "9.8"


def test_parse_gitlab_advisory_malformed_cvss_vectors(tmp_path):
content = {
"identifier": "CVE-2023-0002",
"package_slug": "pypi/django",
"title": "Vulnerability with malformed vectors",
"description": "Test error handling for malformed CVSS vectors",
"pubdate": "2023-01-01",
"affected_range": "<4.0.0",
"fixed_versions": ["4.0.0"],
"urls": ["https://example.com/advisory"],
"cvss_v2": "MALFORMED_CVSS2_VECTOR",
"cvss_v3": "MALFORMED_CVSS3_VECTOR",
"cwe_ids": ["CWE-79"],
"identifiers": ["CVE-2023-0002"],
}
advisory_path = tmp_path / "CVE-2023-0002.yaml"
advisory_path.write_text(saneyaml.dump(content))

logged_errors = []

def recording_logger(msg, level=None):
logged_errors.append((msg, level))

result = parse_gitlab_advisory(
file=advisory_path,
base_path=advisory_path.parent,
gitlab_scheme_by_purl_type={"pypi": "pypi"},
purl_type_by_gitlab_scheme={"pypi": "pypi"},
logger=recording_logger,
)

assert isinstance(result, AdvisoryDataV2)
assert len(result.severities) == 2

assert result.severities[0].system.identifier == "cvssv2"
assert result.severities[0].scoring_elements == "MALFORMED_CVSS2_VECTOR"
assert result.severities[0].value == ""

assert result.severities[1].system.identifier == "cvssv3"
assert result.severities[1].scoring_elements == "MALFORMED_CVSS3_VECTOR"
assert result.severities[1].value == ""

assert len(logged_errors) == 2
assert any("Invalid CVSSv2 vector" in msg for msg, _ in logged_errors)
assert any("Invalid CVSSv3 vector" in msg for msg, _ in logged_errors)