Skip to content
Merged
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
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "vex-reader"
version = "0.9.4.1"
version = "0.9.5"
authors = [
{ name="Vincent Danen", email="vdanen@annvix.com" },
]
Expand All @@ -16,8 +16,8 @@ classifiers = [
]
dependencies = [
"requests==2.32.4",
"rich==13.7.1",
"pytz==2022.4"
"rich>=13.8.1",
"pytz>=2024.1"
]

[project.optional-dependencies]
Expand Down
137 changes: 137 additions & 0 deletions tests/cve-2025-58443.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
{
"document": {
"category": "csaf_vex",
"csaf_version": "2.0",
"distribution": {
"text": "Copyright © Red Hat, Inc. All rights reserved.",
"tlp": {
"label": "WHITE",
"url": "https://www.first.org/tlp/"
}
},
"lang": "en",
"notes": [
{
"category": "legal_disclaimer",
"text": "This content is licensed under the Creative Commons Attribution 4.0 International License (https://creativecommons.org/licenses/by/4.0/). If you distribute this content, or a modified version of it, you must provide attribution to Red Hat Inc. and provide a link to the original.",
"title": "Terms of Use"
}
],
"publisher": {
"category": "vendor",
"contact_details": "https://access.redhat.com/security/team/contact/",
"issuing_authority": "Red Hat Product Security is responsible for vulnerability handling across all Red Hat products and services.",
"name": "Red Hat Product Security",
"namespace": "https://www.redhat.com"
},
"references": [
{
"category": "self",
"summary": "Canonical URL",
"url": "https://security.access.redhat.com/data/csaf/v2/vex/2025/cve-2025-58443.json"
}
],
"title": "FOG's authentication bypass leads to full SQL DB dump",
"tracking": {
"current_release_date": "2025-11-21T10:48:20+00:00",
"generator": {
"date": "2025-11-21T10:48:20+00:00",
"engine": {
"name": "Red Hat SDEngine",
"version": "4.6.12"
}
},
"id": "CVE-2025-58443",
"initial_release_date": "2025-09-06T20:04:25.442000+00:00",
"revision_history": [
{
"date": "2025-09-06T20:04:25.442000+00:00",
"number": "1",
"summary": "Initial version"
},
{
"date": "2025-09-29T13:43:03+00:00",
"number": "2",
"summary": "Current version"
},
{
"date": "2025-11-21T10:48:20+00:00",
"number": "3",
"summary": "Last generated version"
}
],
"status": "final",
"version": "3"
}
},
"product_tree": {
"branches": [
{
"category": "vendor",
"name": "Red Hat",
"product": {
"name": "All currently supported Red Hat products",
"product_id": "red_hat_products",
"product_identification_helper": {
"cpe": "cpe:/a:redhat"
}
}
}
]
},
"vulnerabilities": [
{
"cve": "CVE-2025-58443",
"discovery_date": "2025-09-06T21:00:46.983541+00:00",
"flags": [
{
"label": "vulnerable_code_not_present",
"product_ids": [
"red_hat_products"
]
}
],
"notes": [
{
"category": "description",
"text": "FOG is a free open-source cloning/imaging/rescue suite/inventory management system. Versions 1.5.10.1673 and below contain an authentication bypass vulnerability. It is possible for an attacker to perform an unauthenticated DB dump where they could pull a full SQL DB without credentials. A fix is expected to be released 9/15/2025. To address this vulnerability immediately, upgrade to the latest version of either the dev-branch or working-1.6 branch. This will patch the issue for users concerned about immediate exposure. See the FOG Project documentation for step-by-step upgrade instructions: https://docs.fogproject.org/en/latest/install-fog-server#choosing-a-fog-version.",
"title": "Vulnerability description"
},
{
"category": "other",
"text": "Red Hat Product Security has determined that this vulnerability does not affect any currently supported Red Hat product. This assessment may evolve based on further analysis and discovery. For more information about this vulnerability and the products it affects, please see the linked references.",
"title": "Statement"
}
],
"product_status": {
"known_not_affected": [
"red_hat_products"
]
},
"references": [
{
"category": "self",
"summary": "Canonical URL",
"url": "https://access.redhat.com/security/cve/CVE-2025-58443"
},
{
"category": "external",
"summary": "https://www.cve.org/CVERecord?id=CVE-2025-58443",
"url": "https://www.cve.org/CVERecord?id=CVE-2025-58443"
},
{
"category": "external",
"summary": "https://nvd.nist.gov/vuln/detail/CVE-2025-58443",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-58443"
},
{
"category": "external",
"summary": "https://github.com/FOGProject/fogproject/security/advisories/GHSA-mvwm-9m2h-87p9",
"url": "https://github.com/FOGProject/fogproject/security/advisories/GHSA-mvwm-9m2h-87p9"
}
],
"release_date": "2025-09-06T20:04:25.442000+00:00",
"title": "FOG's authentication bypass leads to full SQL DB dump"
}
]
}
1 change: 1 addition & 0 deletions tests/suse-cve-2014-0160.json

Large diffs are not rendered by default.

133 changes: 86 additions & 47 deletions tests/test_vex1.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,26 +80,6 @@ def test_cvss_vector(self):
def test_cvss_base_score(self):
self.assertEqual(self.vex.global_cvss.baseScore, 8.6)

def test_nvd_cvss_vector(self):
response = requests.get(f'https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={self.cve}')
self.nvd_cve = response.json()
if self.nvd_cve['vulnerabilities'][0]['cve']['id'] == self.cve:
# we got the right result
self.nvd = NVD(self.nvd_cve)
else:
self.nvd = NVD(None)
self.assertEqual(self.nvd.cvss31.vectorString, 'CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H')

def test_nvd_cvss_base_score(self):
response = requests.get(f'https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={self.cve}')
self.nvd_cve = response.json()
if self.nvd_cve['vulnerabilities'][0]['cve']['id'] == self.cve:
# we got the right result
self.nvd = NVD(self.nvd_cve)
else:
self.nvd = NVD(None)
self.assertEqual(self.nvd.cvss31.baseScore, 8.6)

def test_number_of_refs(self):
self.assertEqual(len(self.vex.references), 4)

Expand Down Expand Up @@ -237,26 +217,6 @@ def test_cvss_vector(self):
def test_cvss_base_score(self):
self.assertEqual(self.vex.global_cvss.baseScore, 5.5)

def test_nvd_cvss_vector(self):
response = requests.get(f'https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={self.cve}')
self.nvd_cve = response.json()
if self.nvd_cve['vulnerabilities'][0]['cve']['id'] == self.cve:
# we got the right result
self.nvd = NVD(self.nvd_cve)
else:
self.nvd = NVD(None)
self.assertEqual(self.nvd.cvss31.vectorString, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H')

def test_nvd_cvss_base_score(self):
response = requests.get(f'https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={self.cve}')
self.nvd_cve = response.json()
if self.nvd_cve['vulnerabilities'][0]['cve']['id'] == self.cve:
# we got the right result
self.nvd = NVD(self.nvd_cve)
else:
self.nvd = NVD(None)
self.assertEqual(self.nvd.cvss31.baseScore, 7.5)

def test_number_of_refs(self):
self.assertEqual(len(self.vex.references), 4)

Expand Down Expand Up @@ -299,6 +259,48 @@ def test_cvss_vector(self):
def test_cvss_base_score(self):
self.assertEqual(self.vex.global_cvss.baseScore, 8.1)

def test_number_of_refs(self):
self.assertEqual(len(self.vex.references), 4)

def test_number_of_mitigations(self):
self.assertEqual(len(self.packages.mitigation), 1)

def test_number_of_fixes(self):
self.assertEqual(len(self.packages.fixes), 0)

def test_number_of_affects(self):
self.assertEqual(len(self.packages.affected), 1)

def test_number_of_noaffects(self):
self.assertEqual(len(self.packages.not_affected), 5)


class TestCVE_2025_58443(TestVex):
def setUp(self):
# Use the correct path relative to the tests directory
self.cve = 'CVE-2025-58443'
test_file = os.path.join(os.path.dirname(__file__), f'{self.cve.lower()}.json')
self.vex = Vex(test_file)
self.packages = VexPackages(self.vex.raw)

def test_cve_name(self):
self.assertEqual(self.vex.cve, self.cve)

def test_public_date(self):
self.assertEqual(self.vex.release_date, '2025-09-06')

def test_impact(self):
self.assertEqual(self.vex.global_impact, None)

def test_bzid(self):
self.assertEqual(self.vex.bz_id, None)

def test_cvss_vector(self):
self.assertEqual(self.vex.global_cvss.vectorString, 'NOT AVAILABLE ')

def test_cvss_base_score(self):
self.assertEqual(self.vex.global_cvss.baseScore, '')

def test_nvd_cvss_vector(self):
response = requests.get(f'https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={self.cve}')
self.nvd_cve = response.json()
Expand All @@ -307,8 +309,7 @@ def test_nvd_cvss_vector(self):
self.nvd = NVD(self.nvd_cve)
else:
self.nvd = NVD(None)
# as of 2025/11/27 NVD has not assigned a CVSS
self.assertEqual(self.nvd.cvss31.vectorString, 'NOT AVAILABLE ')
self.assertEqual(self.nvd.cvss31.vectorString, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N')

def test_nvd_cvss_base_score(self):
response = requests.get(f'https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={self.cve}')
Expand All @@ -318,23 +319,61 @@ def test_nvd_cvss_base_score(self):
self.nvd = NVD(self.nvd_cve)
else:
self.nvd = NVD(None)
self.assertEqual(self.nvd.cvss31.baseScore, '')
self.assertEqual(self.nvd.cvss31.baseScore, 9.1)

def test_number_of_refs(self):
self.assertEqual(len(self.vex.references), 4)
self.assertEqual(len(self.vex.references), 3)

def test_number_of_mitigations(self):
self.assertEqual(len(self.packages.mitigation), 1)
self.assertEqual(len(self.packages.mitigation), 0)

def test_number_of_fixes(self):
self.assertEqual(len(self.packages.fixes), 0)

def test_number_of_affects(self):
self.assertEqual(len(self.packages.affected), 1)
self.assertEqual(len(self.packages.affected), 0)

def test_number_of_noaffects(self):
self.assertEqual(len(self.packages.not_affected), 5)
self.assertEqual(len(self.packages.not_affected), 0)


class TestSUSECVE_2014_0160(TestVex):
def setUp(self):
# Use the correct path relative to the tests directory
self.cve = 'CVE-2014-0160'
test_file = os.path.join(os.path.dirname(__file__), f'suse-{self.cve.lower()}.json')
self.vex = Vex(test_file)
self.packages = VexPackages(self.vex.raw)

def test_cve_name(self):
self.assertEqual(self.vex.cve, self.cve)

def test_public_date(self):
self.assertEqual(self.vex.release_date, '2014-04-07')

def test_impact(self):
self.assertEqual(self.vex.global_impact, 'Important')

def test_cvss_vector(self):
self.assertEqual(self.vex.global_cvss.vectorString, 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N')

def test_cvss_base_score(self):
self.assertEqual(self.vex.global_cvss.baseScore, 7.5)

def test_number_of_refs(self):
self.assertEqual(len(self.vex.references), 31)

def test_number_of_mitigations(self):
self.assertEqual(len(self.packages.mitigation), 0)

def test_number_of_fixes(self):
self.assertEqual(len(self.packages.fixes), 1)

def test_number_of_affects(self):
self.assertEqual(len(self.packages.affected), 0)

def test_number_of_noaffects(self):
self.assertEqual(len(self.packages.not_affected), 492)

"""
class TestCVE_Cisco_rce_2024(TestVex):
Expand Down
10 changes: 10 additions & 0 deletions vex/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ def product_lookup(product, pmap):

return (name, cpe, purl)

# our pmap has no products so return an empty tuple
return (name, cpe, purl)


def dedupe(component_list):
return list(dict.fromkeys(component_list))
Expand Down Expand Up @@ -185,6 +188,9 @@ def build_product_tree(self):

self.pmap = []
for p in self.raw['product_tree']['branches']:
if not 'branches' in p:
# there are no product branches, meaning no products
continue
# TODO there seems to be a bug in the VEX output respective to branch nesting, it's very convoluted =(
for b in p['branches']:
if 'branches' in b.keys():
Expand Down Expand Up @@ -250,6 +256,10 @@ def parse_packages(self):
self.affected.append(Affected(y, self.pmap))
if x == 'known_not_affected':
for y in filter_components(k['product_status']['known_not_affected']):
# this is for those "does not affect us CVEs"; at least Red Hat does this so if we see this
# we don't build a not-affects list, we just leave it empty
if y == 'red_hat_products':
continue
# check to make sure we're not adding a dupe
addMe = True
for na in self.not_affected:
Expand Down
Loading
Loading