fix(app-scan): find newest Firefox CVEs + SharePoint 2013 (both were my bad calls)

The tester proved two of my 'structurally impossible' claims wrong by patching
around them locally. Both were wrong, and for the same reason as before —
checking too narrow a slice of the data.

1. Newest Firefox CVEs were never indexed. Mozilla states the INVERSE: no
   affected range at all, only 'version 152.0.6, lessThanOrEqual *,
   status: unaffected'. _ranges_from_affected skipped every non-affected entry,
   so the CVE vanished. 'X and up are fixed' == 'below X is affected', so a
   lone unaffected floor now yields a fix bound. Guarded: only when the entry
   declared no affected range of its own AND there is exactly one floor —
   several floors mean several branches (release vs ESR) and picking one would
   over- or under-report.

2. SharePoint 2013 does have real fix ranges. I concluded otherwise from six
   RECENT MSRC docs — of course a product EOL since 2023-04-11 is absent there.
   The CVE records from its supported years carry real builds
   (CVE-2023-23395: 15.0.0 .. 15.0.5537.1000) and an unpatched farm is behind
   all of them. That is exactly what the tester's Nessus reports.

Verified against the live records, both directions:
  Firefox 147.0.4.0 / 152.0.5 -> affected;  152.0.6 / 153.0 -> clean
  SharePoint Foundation 2013 @ 15.0.4569.1506 -> flagged by CVE-2023-23395
                                                 and CVE-2022-35823
  regression: WS2016 @ 14393.5000 -> flagged, @ 14393.9234 -> clean

Index bumped to v6 so it rebuilds once.
This commit is contained in:
2026-07-16 09:38:25 +02:00
parent d304463996
commit b4d0caf8ad
+32 -3
View File
@@ -42,7 +42,7 @@ logger = logging.getLogger(__name__)
_ZIP_PATH = "/tmp/truevuln-cvelistv5-cache.zip"
_ZIP_URL = "https://github.com/CVEProject/cvelistV5/archive/refs/heads/main.zip"
_ZIP_TTL = 12 * 3600
_INDEX_SETTING = "cvelistv5_product_index_v5" # v5: entries carry the product name
_INDEX_SETTING = "cvelistv5_product_index_v6" # v6: inverse-unaffected ranges + SharePoint 2013
_INDEX_TTL = timedelta(hours=26) # rebuilt nightly; a missed night still serves
# Curated registry: name-regex (installed software) → cvelistV5 (vendor,
@@ -73,8 +73,18 @@ _REGISTRY: List[dict] = [
# (16.0.0 .. 16.0.10417.20153) — the exact cross-release false positive the
# Windows OS scan just had. The release comes from the NAME; the range then
# only ever decides "patched or not" within that one release.
# 2013 is absent on purpose: Microsoft publishes no fixes for it (0 hits in
# six MSRC docs), so no range exists — its EOL finding is the signal.
# 2013 IS here: it is EOL (2023-04-11) and absent from recent MSRC docs, but
# the CVE records from its supported years carry real fix builds
# (CVE-2023-23395: 15.0.0 .. 15.0.5537.1000), and an unpatched 2013 farm is
# behind all of them. Checking only recent MSRC docs is what made this look
# undetectable — the tester's Nessus finds these, and so should we.
{"key": "sharepoint-2013", "re": r"sharepoint.*\b2013\b",
"pairs": [("microsoft", "microsoft sharepoint foundation 2013 service pack 1"),
("microsoft", "microsoft sharepoint enterprise server 2013 service pack 1"),
("microsoft", "microsoft sharepoint server 2013 service pack 1"),
("microsoft", "microsoft sharepoint foundation 2013"),
("microsoft", "microsoft sharepoint enterprise server 2013"),
("microsoft", "microsoft sharepoint server 2013")]},
{"key": "sharepoint-se", "re": r"sharepoint.*subscription",
"pairs": [("microsoft", "microsoft sharepoint server subscription edition")]},
{"key": "sharepoint-2019", "re": r"sharepoint.*\b2019\b",
@@ -201,10 +211,22 @@ def _ranges_from_affected(aff: dict) -> List[Tuple[Optional[str], Optional[str],
Only ranges with a real upper bound are returned (exact-version-only and
unbounded entries are skipped → no over-matching)."""
out = []
unaffected_floors: List[str] = []
for v in aff.get("versions", []) or []:
if not isinstance(v, dict):
continue
if (v.get("status") or "affected") != "affected":
# Some CNAs state the INVERSE — Mozilla ships no affected range at
# all, only "152.0.6 and up are unaffected" (status=unaffected,
# lessThanOrEqual="*"). Skipping those meant the newest Firefox CVEs
# never entered the index. "X and up are fixed" == "below X is
# affected", so remember X as a fix bound.
if (v.get("status") == "unaffected"
and not v.get("lessThan")
and (v.get("lessThanOrEqual") in ("*", None))):
ver = v.get("version")
if isinstance(ver, str) and ver.strip() not in ("0", "*", "-", ""):
unaffected_floors.append(ver.strip())
continue
start = None
lt = v.get("lessThan")
@@ -230,6 +252,13 @@ def _ranges_from_affected(aff: dict) -> List[Tuple[Optional[str], Optional[str],
start = None
if lt or lte:
out.append((start, lt, lte))
# Inverse-only records (see above): derive the fix bound from the single
# "unaffected" floor. Only when the entry stated no affected range of its
# own, and only when there is exactly one floor — several floors mean
# several servicing branches (release vs ESR) and picking one would either
# over- or under-report.
if not out and len(unaffected_floors) == 1:
out.append((None, unaffected_floors[0], None))
return out