feat(app-cve-scan): iOS/iPadOS OS-level CVEs
Apple ships the precise OS version (e.g. 18.1.2) and NVD carries proper version ranges for cpe:2.3:o:apple:iphone_os / :ipados, so it's the same clean CPE-range check the desktop apps already use — no new machinery. Reads asset.operating_system + asset.os_version (already synced from Intune), so it covers Intune-only iPhones/iPads that have no syscollector packages. Runs per asset regardless of package inventory; cached per (cpe, version) so N devices on the same iOS build = one NVD query. Android intentionally omitted: NVD only lists the base version (13/14/15) without ranges → needs the Intune security-patch level + Android bulletin parsing, a separate feature.
This commit is contained in:
@@ -75,6 +75,28 @@ _REGISTRY: List[tuple] = [
|
||||
]
|
||||
|
||||
|
||||
# OS-level CPEs. Apple ships the precise OS version (e.g. 18.1.2) and NVD
|
||||
# carries proper version ranges for it → a clean CPE-range check, same as the
|
||||
# desktop apps. Android is deliberately absent: NVD only lists the base
|
||||
# version (13/14/15) without ranges, so it needs the Intune security-patch
|
||||
# level + Android bulletin parsing — a separate feature.
|
||||
# ponytail: iOS/iPadOS only; add Android when the patch-level path is built.
|
||||
_OS_REGISTRY: List[tuple] = [
|
||||
(re.compile(r"ipad", re.I), {"key": "cpe:apple:ipados",
|
||||
"cpe": "cpe:2.3:o:apple:ipados", "label": "Apple iPadOS"}),
|
||||
(re.compile(r"ios|iphone", re.I), {"key": "cpe:apple:iphone_os",
|
||||
"cpe": "cpe:2.3:o:apple:iphone_os", "label": "Apple iOS"}),
|
||||
]
|
||||
|
||||
|
||||
def _resolve_os(os_name: str) -> Optional[dict]:
|
||||
n = (os_name or "").lower()
|
||||
for rx, e in _OS_REGISTRY:
|
||||
if rx.search(n):
|
||||
return e
|
||||
return None
|
||||
|
||||
|
||||
def resolve_product(name: str) -> Optional[dict]:
|
||||
n = (name or "").strip().lower()
|
||||
if not n:
|
||||
@@ -377,6 +399,31 @@ def scan_asset_packages(db: Session, asset, packages: list, new_ids: Optional[li
|
||||
return count
|
||||
|
||||
|
||||
def scan_asset_os(db: Session, asset, new_ids: list) -> int:
|
||||
"""OS-level CVEs from asset.operating_system + asset.os_version
|
||||
(iOS/iPadOS). Returns findings. Caller commits."""
|
||||
e = _resolve_os(asset.operating_system or "")
|
||||
if not e:
|
||||
return 0
|
||||
cver = _clean_version(asset.os_version or "")
|
||||
if not cver:
|
||||
return 0
|
||||
try:
|
||||
cves = lookup_cves(db, {"key": e["key"], "kind": "cpe", "cpe": e["cpe"]}, cver)
|
||||
except Exception as ex:
|
||||
logger.debug("app-cve OS lookup failed for %s: %s", asset.hostname, ex)
|
||||
return 0
|
||||
count = 0
|
||||
for c in cves:
|
||||
before = len(new_ids)
|
||||
try:
|
||||
_upsert(db, asset, e["label"], asset.os_version or cver, c, new_ids)
|
||||
count += 1 if len(new_ids) > before else 0
|
||||
except Exception as ex:
|
||||
logger.debug("app-cve OS upsert failed (%s on %s): %s", c.get("cve"), asset.id, ex)
|
||||
return count
|
||||
|
||||
|
||||
def run_app_cve_scan(db: Session, asset_id: Optional[int] = None) -> dict:
|
||||
"""Scan all assets with software inventory (Wazuh packages + Intune
|
||||
detectedApps) → app-scan CVEs, then enrich the new ones."""
|
||||
@@ -412,6 +459,17 @@ def run_app_cve_scan(db: Session, asset_id: Optional[int] = None) -> dict:
|
||||
if asset_id is not None:
|
||||
q = q.filter(Asset.id == asset_id)
|
||||
for asset in q.all():
|
||||
touched = False
|
||||
# OS-level CVEs (iOS/iPadOS) — version already in the DB, no client needed.
|
||||
try:
|
||||
n = scan_asset_os(db, asset, new_ids)
|
||||
if n:
|
||||
stats["findings"] += n
|
||||
touched = True
|
||||
except Exception as e:
|
||||
stats["errors"].append(f"asset {asset.id} os: {e}")
|
||||
|
||||
# Package-level CVEs (Wazuh syscollector / Intune detectedApps).
|
||||
packages: list = []
|
||||
try:
|
||||
if asset.wazuh_agent_id and wazuh:
|
||||
@@ -421,11 +479,13 @@ def run_app_cve_scan(db: Session, asset_id: Optional[int] = None) -> dict:
|
||||
except Exception as e:
|
||||
stats["errors"].append(f"asset {asset.id}: {e}")
|
||||
packages = []
|
||||
if not packages:
|
||||
continue
|
||||
stats["assets"] += 1
|
||||
stats["findings"] += scan_asset_packages(db, asset, packages, new_ids)
|
||||
db.commit()
|
||||
if packages:
|
||||
stats["findings"] += scan_asset_packages(db, asset, packages, new_ids)
|
||||
touched = True
|
||||
|
||||
if touched:
|
||||
stats["assets"] += 1
|
||||
db.commit()
|
||||
|
||||
if graph:
|
||||
graph.close()
|
||||
|
||||
Reference in New Issue
Block a user