With the real syscollector rows in hand the level-only match turns out to be
a false-positive generator. Every SAP release carries the same patch level
NUMBERS — NVD lists gui_for_windows 7.70:patch_level17 right next to
8.0:patch_level1 — so the tester's host (SAP GUI for Windows 8.00 64bit,
Patch 17) matched every 7.70 CVE that happens to have a patch_level17 entry.
The release is now compared as well, and numerically: Wazuh reports "8.00"
where NVD writes "8.0", so a string compare would have rejected the host's
own release.
The parser is confirmed against the verbatim inventory strings, which differ
per product — Business Client states the level in the version field
("8.00 PL26") while SAP GUI states a compilation there and puts the level in
the NAME ("SAP GUI for Windows 8.00 64bit (Patch 17)"). Both are pinned in
the test, along with "Compilation" not reading as a level despite containing
the letters p-l.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
74 lines
3.5 KiB
Python
74 lines
3.5 KiB
Python
"""SAP patch-level matching — run: python tests/test_sap_patch_level.py
|
|
|
|
SAP enumerates one CPE per patch level instead of stating a range, so
|
|
"affected" is set membership. The dangerous failure is silent: if the level
|
|
can't be read, the base release (7.70) matches every CVE ever filed against
|
|
it. Unknown level must therefore report NOTHING.
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from app.services.app_cve_scanner_service import _sap_affected, _sap_patch_level
|
|
|
|
|
|
def demo():
|
|
# VERBATIM syscollector rows from the tester's SAP host — the level lives
|
|
# in the version field for Business Client and in the NAME for SAP GUI,
|
|
# whose version field states a compilation instead.
|
|
assert _sap_patch_level("SAP Business Client 8.00", "8.00 PL26") == 26
|
|
assert _sap_patch_level("SAP GUI for Windows 8.00 64bit (Patch 17)",
|
|
"8.00 Compilation 1") == 17
|
|
assert _sap_patch_level("Adobe LiveCycle Designer 11.0 for SAP solutions",
|
|
"11.0 Patch Level 37") == 37
|
|
# "Compilation" contains the letters p-l; it must not read as a level.
|
|
assert _sap_patch_level("SAP GUI for Windows 8.00", "8.00 Compilation 1") is None
|
|
# Other spellings from wazuh/wazuh#30334.
|
|
assert _sap_patch_level("SAP GUI for Windows 7.70", "7.70 PL 12") == 12
|
|
assert _sap_patch_level("SAP Business Client 6.0", "6.0 Patch Level 16") == 16
|
|
# No level stated anywhere → unknown, never a guess.
|
|
assert _sap_patch_level("SAP Business Client", "6.0") is None
|
|
assert _sap_patch_level("", "") is None
|
|
|
|
# Real criteria strings from CVE-2023-32113 / CVE-2021-38150.
|
|
pl4 = {"criteria": "cpe:2.3:a:sap:gui_for_windows:7.70:patch_level4:*:*:*:*:*:*"}
|
|
pl17 = {"criteria": "cpe:2.3:a:sap:gui_for_windows:7.70:patch_level17:*:*:*:*:*:*"}
|
|
v8pl1 = {"criteria": "cpe:2.3:a:sap:gui_for_windows:8.0:patch_level1:*:*:*:*:*:*"}
|
|
pl10 = {"criteria": "cpe:2.3:a:sap:business_client:6.0:patch_level10:*:*:*:*:*:*"}
|
|
base = {"criteria": "cpe:2.3:a:sap:business_client:6.0:-:*:*:*:*:*:*"}
|
|
|
|
assert _sap_affected(pl4, "7.70", 4)
|
|
assert not _sap_affected(pl4, "7.70", 5) # patched past it
|
|
assert not _sap_affected(pl4, "7.70", 3) # a DIFFERENT level, not "older"
|
|
assert _sap_affected(pl10, "6.0", 10)
|
|
assert _sap_affected(base, "6.0", 0) # ':-' is the unpatched base
|
|
assert not _sap_affected(base, "6.0", 1)
|
|
|
|
# The release has to match too. Every release carries the same level
|
|
# NUMBERS, so level-only matching drops a 7.70 CVE onto an 8.00 host —
|
|
# exactly the tester's box (SAP GUI 8.00, Patch 17).
|
|
assert not _sap_affected(pl17, "8.00", 17)
|
|
assert _sap_affected(pl17, "7.70", 17)
|
|
# Wazuh says "8.00", NVD says "8.0" — same release, compared numerically.
|
|
assert _sap_affected(v8pl1, "8.00", 1)
|
|
assert not _sap_affected(v8pl1, "7.70", 1)
|
|
|
|
# The whole point: unknown level matches nothing at all.
|
|
for m in (pl4, pl10, base, v8pl1):
|
|
assert not _sap_affected(m, "7.70", None)
|
|
|
|
# A SAP CPE with a WILDCARD update field states no patch level, so it says
|
|
# nothing about patch state — CVE-2021-38175 lists
|
|
# `analysis_for_microsoft_office:2.8:*` with no range at all, and treating
|
|
# that as a hit marks every 2.8 build affected forever. Never match it.
|
|
wild = {"criteria": "cpe:2.3:a:sap:analysis_for_microsoft_office:2.8:*:*:*:*:*:*:*"}
|
|
for pl in (None, 0, 3, 99):
|
|
assert not _sap_affected(wild, "2.8", pl)
|
|
|
|
print("sap patch level OK")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
demo()
|