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:
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user