- Add VulnOverrideService to correct erroneous Wazuh CVSS scores (e.g. 10.0 placeholder)
- Add CISA Vulnrichment integration for verified CVSSv3.1 scores and SSVC exploitation status
- Add new API endpoints:
- GET /override/check - find incorrect scores
- POST /override/nessus/{asset_id} - override from Nessus
- POST /override/vulnrichment - override from CISA (all CVEs)
- GET /override/stats - score error statistics
- Add exploitation_status (SSVC: none/poc/active/widespread) and exploitation_source columns
- Add 'Correct CVSS' button in vulnerabilities frontend
- Add SSVC badge display in vulnerability table
- Update Vulnerability model and types
Fixes: CVE-2026-8390 incorrect CVSS 10.0 → 7.3 HIGH
643 lines
24 KiB
Python
643 lines
24 KiB
Python
"""
|
|
Vulnerability Score Override Service
|
|
|
|
Korrigiert fehlerhafte CVSS-Scores und Severity-Werte aus Wazuh mit den
|
|
verifizierten Daten aus Nessus/Vulnrichment JSON.
|
|
|
|
Problem: Wazuh liefert manchmal falsche CVSS-Werte (z.B. 10.0 für CVE-2026-8390)
|
|
während die korrekten Werte (z.B. 7.3 HIGH) aus Nessus/Vulnrichment stammen.
|
|
|
|
Funktionsweise:
|
|
1. Lädt verifizierte CVE-Daten aus Nessus Plugin-Output
|
|
2. Vergleicht mit bestehenden Datenbankeinträgen
|
|
3. Überschreibt fehlerhafte Wazuh-Werte mit korrekten Nessus/Vulnrichment-Werten
|
|
4. Aktualisiert auch Severity und Exploitation-Status (SSVC)
|
|
"""
|
|
import json
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import Dict, List, Optional, Set, Tuple
|
|
from dataclasses import dataclass
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models.vulnerability import (
|
|
Vulnerability,
|
|
VulnerabilitySeverity,
|
|
VulnerabilityStatus,
|
|
)
|
|
from app.models.asset import Asset
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# SSVC Exploitation Status
|
|
class SSVCExploitationStatus(str):
|
|
"""SSVC Exploitation Kategorien (Draft NIST SP 500-299)"""
|
|
NONE = "none" # Keine Aktivitäten bekannt
|
|
POC = "poc" # Proof-of-Concept vorhanden
|
|
ACTIVE = "active" # Aktive Ausnutzung
|
|
WIDESPREAD = "widespread" # Weit verbreitete Ausnutzung
|
|
|
|
|
|
@dataclass
|
|
class VerifiedCVEData:
|
|
"""Container für verifizierte CVE-Daten aus einer verlässlichen Quelle"""
|
|
cve_id: str
|
|
cvss_score: Optional[float] = None
|
|
cvss_vector: Optional[str] = None
|
|
severity: Optional[str] = None # critical, high, medium, low, none
|
|
exploitation_status: Optional[str] = None # none, poc, active, widespread
|
|
epss_score: Optional[float] = None
|
|
exploit_available: Optional[bool] = None
|
|
exploit_maturity: Optional[str] = None # Unproven, PoC, Functional, High
|
|
vpr_score: Optional[float] = None
|
|
description: Optional[str] = None
|
|
references: Optional[List[str]] = None
|
|
source: str = "unknown" # nessus, nvd, vulrighment, cisa
|
|
|
|
|
|
class VulnOverrideService:
|
|
"""
|
|
Service zum Abgleich und Korrektur von CVE-Scores.
|
|
|
|
Verwendet verifizierte Daten aus:
|
|
- Nessus Plugin Output (plugin_cvss, exploit_available, etc.)
|
|
- CISA Vulnrichment JSON Feed
|
|
- NVD (als Fallback)
|
|
|
|
Wichtig: Die Korrektur priorisiert folgende Reihenfolge:
|
|
1. Nessus (direkte Scan-Daten, höchste Vertrauenswürdigkeit)
|
|
2. CISA Vulnrichment (offizielle govt. Quelle)
|
|
3. NVD (offizielle Quelle)
|
|
"""
|
|
|
|
def __init__(self, db: Session):
|
|
self.db = db
|
|
|
|
def _severity_from_cvss(self, cvss_score: Optional[float]) -> VulnerabilitySeverity:
|
|
"""Konvertiert CVSS-Score zu Severity-Enum"""
|
|
if cvss_score is None:
|
|
return VulnerabilitySeverity.none
|
|
if cvss_score >= 9.0:
|
|
return VulnerabilitySeverity.critical
|
|
elif cvss_score >= 7.0:
|
|
return VulnerabilitySeverity.high
|
|
elif cvss_score >= 4.0:
|
|
return VulnerabilitySeverity.medium
|
|
elif cvss_score > 0:
|
|
return VulnerabilitySeverity.low
|
|
return VulnerabilitySeverity.none
|
|
|
|
def _is_wazuh_placeholder_score(self, score: Optional[float]) -> bool:
|
|
"""
|
|
Erkennt fehlerhafte Wazuh-Platzhalter-Scores.
|
|
|
|
Wazuh liefert manchmal 10.0 als Platzhalter, wenn der echte
|
|
CVSS-Score unbekannt oder nicht verfügbar ist.
|
|
"""
|
|
if score is None:
|
|
return False
|
|
# 10.0 ist ein bekannter Platzhalter-Wert in Wazuh
|
|
# Echte CVSS-Werte gehen nur bis 10.0, aber 10.0 selbst ist selten
|
|
# Besonders bei plausiblen Scores wie 7.3 ist 10.0 offensichtlich falsch
|
|
return score == 10.0
|
|
|
|
def _is_score_discrepancy(
|
|
self,
|
|
wazuh_score: Optional[float],
|
|
verified_score: Optional[float],
|
|
threshold: float = 1.0
|
|
) -> bool:
|
|
"""
|
|
Prüft ob eine signifikante Diskrepanz zwischen Scores besteht.
|
|
|
|
Args:
|
|
wazuh_score: Aktueller Score in DB (von Wazuh)
|
|
verified_score: Korrekter Score aus verifizierter Quelle
|
|
threshold: Minimale Differenz für Korrektur (Standard: 1.0)
|
|
"""
|
|
if wazuh_score is None and verified_score is None:
|
|
return False
|
|
if wazuh_score is None or verified_score is None:
|
|
return True # Einer ist None, der andere hat einen Wert
|
|
|
|
diff = abs(wazuh_score - verified_score)
|
|
return diff >= threshold
|
|
|
|
def load_nessus_verified_data(
|
|
self,
|
|
asset_id: int,
|
|
scan_id: int,
|
|
client
|
|
) -> Dict[str, VerifiedCVEData]:
|
|
"""
|
|
Lädt verifizierte CVE-Daten direkt aus Nessus Plugin Output.
|
|
|
|
Args:
|
|
asset_id: Asset-DB-ID
|
|
scan_id: Nessus Scan-ID
|
|
client: NessusClient Instance
|
|
|
|
Returns:
|
|
Dict[cve_id -> VerifiedCVEData] mit allen verifizierten CVEs
|
|
"""
|
|
verified: Dict[str, VerifiedCVEData] = {}
|
|
|
|
try:
|
|
hosts = client.get_scan_hosts(scan_id)
|
|
for host in hosts:
|
|
host_id = host.get("host_id")
|
|
if not host_id:
|
|
continue
|
|
|
|
# Asset-Matching prüfen
|
|
asset = self.db.query(Asset).filter(
|
|
Asset.id == asset_id
|
|
).first()
|
|
|
|
if not asset:
|
|
continue
|
|
|
|
# Host-Details abrufen
|
|
try:
|
|
host_detail = client.get_host(scan_id, host_id)
|
|
except Exception:
|
|
continue
|
|
|
|
for vuln_entry in host_detail.get("vulnerabilities") or []:
|
|
plugin_id = vuln_entry.get("plugin_id")
|
|
if not plugin_id:
|
|
continue
|
|
|
|
# Plugin-Details abrufen
|
|
try:
|
|
plugin_payload = client.get_plugin_output(
|
|
scan_id, host_id, plugin_id
|
|
)
|
|
except Exception:
|
|
continue
|
|
|
|
# CVEs aus Plugin extrahieren
|
|
cve_list = client.extract_cves(plugin_payload)
|
|
if not cve_list:
|
|
continue
|
|
|
|
# Scores aus Plugin extrahieren
|
|
cvss_score = client.plugin_cvss(plugin_payload)
|
|
vpr_score = client.plugin_vpr_score(plugin_payload)
|
|
exploit_avail = client.plugin_exploit_available(plugin_payload)
|
|
exploit_mat = client.plugin_exploit_maturity(plugin_payload)
|
|
description = client.plugin_description(plugin_payload)
|
|
see_also = client.plugin_see_also(plugin_payload)
|
|
|
|
for cve_id in cve_list:
|
|
if cve_id not in verified: # Nur erster Fund zählt
|
|
verified[cve_id] = VerifiedCVEData(
|
|
cve_id=cve_id,
|
|
cvss_score=cvss_score,
|
|
severity=self._severity_from_cvss(cvss_score).value,
|
|
exploitation_status=self._map_exploit_maturity(exploit_mat),
|
|
exploit_available=exploit_avail,
|
|
exploit_maturity=exploit_mat,
|
|
vpr_score=vpr_score,
|
|
description=description,
|
|
references=see_also,
|
|
source="nessus"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Laden Nessus-Daten für Asset {asset_id}: {e}")
|
|
|
|
return verified
|
|
|
|
def _map_exploit_maturity(self, maturity: Optional[str]) -> str:
|
|
"""Konvertiert Nessus exploit_code_maturity zu SSVC exploitation status"""
|
|
if not maturity:
|
|
return SSVCExploitationStatus.NONE
|
|
|
|
maturity_lower = maturity.lower()
|
|
|
|
if maturity_lower in ("unproven", "poc", "proof-of-concept"):
|
|
return SSVCExploitationStatus.POC
|
|
elif maturity_lower in ("functional", "active"):
|
|
return SSVCExploitationStatus.ACTIVE
|
|
elif maturity_lower == "high":
|
|
return SSVCExploitationStatus.WIDESPREAD
|
|
|
|
return SSVCExploitationStatus.NONE
|
|
|
|
def load_cisa_vulnrichment_data(self, cve_ids: List[str]) -> Dict[str, VerifiedCVEData]:
|
|
"""
|
|
Lädt verifizierte CVE-Daten aus dem CISA Vulnrichment JSON Feed.
|
|
|
|
CISA Vulnrichment enthält korrekte CVSSv3.1 Base Scores und SSVC-Daten.
|
|
Feed: https://www.cisa.gov/sites/default/files/feeds/vulnrichment.json
|
|
|
|
Args:
|
|
cve_ids: Liste von CVE-IDs zum Abrufen
|
|
|
|
Returns:
|
|
Dict[cve_id -> VerifiedCVEData]
|
|
"""
|
|
verified: Dict[str, VerifiedCVEData] = {}
|
|
|
|
try:
|
|
import httpx
|
|
# Vulnrichment API (neuerer Feed mit SSVC-Daten)
|
|
# Alternativ: direkter JSON-Download
|
|
url = "https://www.cisa.gov/sites/default/files/feeds/vulnrichment.json"
|
|
|
|
with httpx.Client(timeout=30.0) as client:
|
|
response = client.get(url)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
|
|
# Durchsuche Feeds nach匹配 CVE-IDs
|
|
for item in data.get("vulnerabilities", []):
|
|
cve_id = item.get("cveID", "").upper()
|
|
|
|
if cve_id not in cve_ids:
|
|
continue
|
|
|
|
# CVSSv3.1 Base Score
|
|
cvss_v31 = item.get("cvssV3_1", {})
|
|
base_score = cvss_v31.get("baseScore")
|
|
base_severity = cvss_v31.get("baseSeverity", "").lower()
|
|
|
|
# SSVC Daten
|
|
ssvc = item.get("ssvc", {})
|
|
exploitation = ssvc.get("exploitation", "").lower()
|
|
|
|
verified[cve_id] = VerifiedCVEData(
|
|
cve_id=cve_id,
|
|
cvss_score=base_score,
|
|
severity=base_severity,
|
|
exploitation_status=exploitation,
|
|
source="vulnrichment"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Laden CISA Vulnrichment-Daten: {e}")
|
|
|
|
return verified
|
|
|
|
def apply_overrides(
|
|
self,
|
|
asset_id: int,
|
|
verified_data: Dict[str, VerifiedCVEData],
|
|
dry_run: bool = False
|
|
) -> dict:
|
|
"""
|
|
Wendet Overrides auf Vulnerabilities an.
|
|
|
|
Args:
|
|
asset_id: Asset-DB-ID
|
|
verified_data: Dict mit verifizierten CVE-Daten
|
|
dry_run: Wenn True, nur simulieren ohne DB-Änderungen
|
|
|
|
Returns:
|
|
Stats-Dict mit Anzahl der Änderungen
|
|
"""
|
|
stats = {
|
|
"checked": 0,
|
|
"updated": 0,
|
|
"skipped_no_change": 0,
|
|
"skipped_placeholder": 0,
|
|
"errors": 0,
|
|
"changes": [] # Liste der durchgeführten Änderungen
|
|
}
|
|
|
|
vulns = self.db.query(Vulnerability).filter(
|
|
Vulnerability.asset_id == asset_id,
|
|
Vulnerability.cve_id.in_(list(verified_data.keys()))
|
|
).all()
|
|
|
|
for vuln in vulns:
|
|
stats["checked"] += 1
|
|
cve_id = vuln.cve_id
|
|
|
|
if cve_id not in verified_data:
|
|
continue
|
|
|
|
verified = verified_data[cve_id]
|
|
|
|
try:
|
|
changes = self._apply_single_override(vuln, verified, dry_run)
|
|
|
|
if changes["has_changes"]:
|
|
if dry_run:
|
|
stats["skipped_no_change"] += 1 # Dry run zählt als "keine Änderung"
|
|
else:
|
|
stats["updated"] += 1
|
|
stats["changes"].append({
|
|
"cve_id": cve_id,
|
|
"vuln_id": vuln.id,
|
|
"fields_changed": changes["fields"],
|
|
"old_values": changes["old"],
|
|
"new_values": changes["new"]
|
|
})
|
|
else:
|
|
stats["skipped_no_change"] += 1
|
|
|
|
except Exception as e:
|
|
stats["errors"] += 1
|
|
logger.error(f"Override für {cve_id} fehlgeschlagen: {e}")
|
|
|
|
if not dry_run:
|
|
self.db.commit()
|
|
|
|
return stats
|
|
|
|
def _apply_single_override(
|
|
self,
|
|
vuln: Vulnerability,
|
|
verified: VerifiedCVEData,
|
|
dry_run: bool = False
|
|
) -> dict:
|
|
"""
|
|
Wendet ein Override auf eine einzelne Vulnerability an.
|
|
|
|
Returns:
|
|
Dict mit has_changes, fields, old, new
|
|
"""
|
|
changes = {
|
|
"has_changes": False,
|
|
"fields": [],
|
|
"old": {},
|
|
"new": {}
|
|
}
|
|
|
|
# 1. CVSS-Score prüfen und ggf. korrigieren
|
|
wazuh_score = vuln.cvss_score
|
|
verified_score = verified.cvss_score
|
|
|
|
# Nur korrigieren wenn:
|
|
# - Wazuh einen Platzhalter liefert (10.0)
|
|
# - ODER signifikante Diskrepanz besteht (>1.0 Differenz)
|
|
should_override_score = (
|
|
self._is_wazuh_placeholder_score(wazuh_score) or
|
|
self._is_score_discrepancy(wazuh_score, verified_score)
|
|
)
|
|
|
|
if should_override_score and verified_score is not None:
|
|
if verified_score != wazuh_score:
|
|
changes["fields"].append("cvss_score")
|
|
changes["old"]["cvss_score"] = wazuh_score
|
|
changes["new"]["cvss_score"] = verified_score
|
|
if not dry_run:
|
|
vuln.cvss_score = verified_score
|
|
changes["has_changes"] = True
|
|
|
|
# 2. Severity korrigieren basierend auf korrektem CVSS
|
|
if verified_score is not None:
|
|
correct_severity = self._severity_from_cvss(verified_score)
|
|
if vuln.severity != correct_severity:
|
|
changes["fields"].append("severity")
|
|
changes["old"]["severity"] = vuln.severity.value
|
|
changes["new"]["severity"] = correct_severity.value
|
|
if not dry_run:
|
|
vuln.severity = correct_severity
|
|
changes["has_changes"] = True
|
|
|
|
# 3. Exploitation-Status (SSVC) aktualisieren
|
|
if verified.exploitation_status:
|
|
current_exploit = getattr(vuln, 'exploitation_status', None)
|
|
if current_exploit != verified.exploitation_status:
|
|
changes["fields"].append("exploitation_status")
|
|
changes["old"]["exploitation_status"] = current_exploit
|
|
changes["new"]["exploitation_status"] = verified.exploitation_status
|
|
if not dry_run:
|
|
vuln.exploitation_status = verified.exploitation_status
|
|
changes["has_changes"] = True
|
|
|
|
# 4. Exploit-Available-Flag aktualisieren
|
|
if verified.exploit_available is not None:
|
|
if vuln.exploit_available != verified.exploit_available:
|
|
changes["fields"].append("exploit_available")
|
|
changes["old"]["exploit_available"] = vuln.exploit_available
|
|
changes["new"]["exploit_available"] = verified.exploit_available
|
|
if not dry_run:
|
|
vuln.exploit_available = verified.exploit_available
|
|
changes["has_changes"] = True
|
|
|
|
# 5. Exploit-Maturity aktualisieren
|
|
if verified.exploit_maturity:
|
|
if vuln.exploit_maturity != verified.exploit_maturity:
|
|
changes["fields"].append("exploit_maturity")
|
|
changes["old"]["exploit_maturity"] = vuln.exploit_maturity
|
|
changes["new"]["exploit_maturity"] = verified.exploit_maturity
|
|
if not dry_run:
|
|
vuln.exploit_maturity = verified.exploit_maturity
|
|
changes["has_changes"] = True
|
|
|
|
# 6. VPR-Score aktualisieren (Tenable)
|
|
if verified.vpr_score is not None:
|
|
if vuln.nessus_vpr_score != verified.vpr_score:
|
|
changes["fields"].append("nessus_vpr_score")
|
|
changes["old"]["nessus_vpr_score"] = vuln.nessus_vpr_score
|
|
changes["new"]["nessus_vpr_score"] = verified.vpr_score
|
|
if not dry_run:
|
|
vuln.nessus_vpr_score = verified.vpr_score
|
|
changes["has_changes"] = True
|
|
|
|
return changes
|
|
|
|
def correct_all_vulnerabilities(
|
|
self,
|
|
dry_run: bool = False
|
|
) -> dict:
|
|
"""
|
|
Korrigiert alle Vulnerabilities in der Datenbank.
|
|
|
|
Verwendet CISA Vulnrichment als primäre Korrekturquelle.
|
|
|
|
Args:
|
|
dry_run: Wenn True, nur simulieren
|
|
|
|
Returns:
|
|
Aggregierte Stats für alle Korrekturen
|
|
"""
|
|
# Alle offenen CVEs sammeln
|
|
vulns = self.db.query(Vulnerability).filter(
|
|
Vulnerability.status == VulnerabilityStatus.open
|
|
).all()
|
|
|
|
cve_ids = list(set(v.cve_id for v in vulns if v.cve_id and not v.cve_id.startswith("NESSUS-")))
|
|
|
|
if not cve_ids:
|
|
return {"checked": 0, "updated": 0, "message": "Keine CVEs zum Korrigieren"}
|
|
|
|
# Vulnrichment-Daten laden
|
|
verified_data = self.load_cisa_vulnrichment_data(cve_ids)
|
|
|
|
# Override anwenden pro Asset
|
|
asset_ids = list(set(v.asset_id for v in vulns))
|
|
|
|
total_stats = {
|
|
"checked": 0,
|
|
"updated": 0,
|
|
"skipped_no_change": 0,
|
|
"skipped_placeholder": 0,
|
|
"errors": 0,
|
|
"assets_processed": len(asset_ids),
|
|
"changes": []
|
|
}
|
|
|
|
for asset_id in asset_ids:
|
|
asset_vulns = [v for v in vulns if v.asset_id == asset_id]
|
|
asset_verified = {
|
|
cve_id: verified_data[cve_id]
|
|
for cve_id in (v.cve_id for v in asset_vulns)
|
|
if cve_id in verified_data
|
|
}
|
|
|
|
if not asset_verified:
|
|
continue
|
|
|
|
stats = self.apply_overrides(asset_id, asset_verified, dry_run)
|
|
total_stats["checked"] += stats["checked"]
|
|
total_stats["updated"] += stats["updated"]
|
|
total_stats["skipped_no_change"] += stats["skipped_no_change"]
|
|
total_stats["skipped_placeholder"] += stats["skipped_placeholder"]
|
|
total_stats["errors"] += stats["errors"]
|
|
total_stats["changes"].extend(stats["changes"])
|
|
|
|
return total_stats
|
|
|
|
def find_incorrect_scores(self) -> List[dict]:
|
|
"""
|
|
Findet alle Vulnerabilities mit wahrscheinlich falschen Scores.
|
|
|
|
Erkennt:
|
|
- Platzhalter-Scores (10.0)
|
|
- Unplausible Kombinationen (CVSS 10 + LOW Severity)
|
|
|
|
Returns:
|
|
Liste von Vulnerabilities mit Diskrepanzen
|
|
"""
|
|
incorrect = []
|
|
|
|
# 1. Alle mit Score 10.0 (Platzhalter)
|
|
placeholder_vulns = self.db.query(Vulnerability).filter(
|
|
Vulnerability.cvss_score == 10.0
|
|
).all()
|
|
|
|
for vuln in placeholder_vulns:
|
|
correct_severity = self._severity_from_cvss(7.3) # Typischer Korrekturwert
|
|
if vuln.severity == VulnerabilitySeverity.critical:
|
|
incorrect.append({
|
|
"vuln_id": vuln.id,
|
|
"cve_id": vuln.cve_id,
|
|
"asset_id": vuln.asset_id,
|
|
"current_score": vuln.cvss_score,
|
|
"current_severity": vuln.severity.value,
|
|
"likely_correct_score": None, # Muss aus externer Quelle ermittelt werden
|
|
"likely_correct_severity": None,
|
|
"issue": "placeholder_score_10",
|
|
"recommendation": "Fetch from CISA Vulnrichment or NVD"
|
|
})
|
|
|
|
# 2. Alle mit CVSS > 9 aber LOW/NONE Severity
|
|
misaligned = self.db.query(Vulnerability).filter(
|
|
Vulnerability.cvss_score >= 9.0,
|
|
Vulnerability.severity.in_([
|
|
VulnerabilitySeverity.low,
|
|
VulnerabilitySeverity.none,
|
|
VulnerabilitySeverity.medium
|
|
])
|
|
).all()
|
|
|
|
for vuln in misaligned:
|
|
incorrect.append({
|
|
"vuln_id": vuln.id,
|
|
"cve_id": vuln.cve_id,
|
|
"asset_id": vuln.asset_id,
|
|
"current_score": vuln.cvss_score,
|
|
"current_severity": vuln.severity.value,
|
|
"issue": "severity_mismatch",
|
|
"recommendation": "CVSS >= 9.0 sollte CRITICAL sein"
|
|
})
|
|
|
|
return incorrect
|
|
|
|
|
|
def correct_vulnerability_scores(
|
|
db: Session,
|
|
cve_ids: Optional[List[str]] = None,
|
|
asset_ids: Optional[List[int]] = None,
|
|
dry_run: bool = False
|
|
) -> dict:
|
|
"""
|
|
Hauptfunktion zum Korrigieren von CVE-Scores.
|
|
|
|
Args:
|
|
db: Datenbank-Session
|
|
cve_ids: Optionale CVE-Filterliste
|
|
asset_ids: Optionale Asset-Filterliste
|
|
dry_run: Simulation ohne DB-Änderungen
|
|
|
|
Returns:
|
|
Statistik-Dict mit Korrekturdetails
|
|
"""
|
|
service = VulnOverrideService(db)
|
|
|
|
query = db.query(Vulnerability).filter(
|
|
Vulnerability.status == VulnerabilityStatus.open
|
|
)
|
|
|
|
if cve_ids:
|
|
query = query.filter(Vulnerability.cve_id.in_(cve_ids))
|
|
if asset_ids:
|
|
query = query.filter(Vulnerability.asset_id.in_(asset_ids))
|
|
|
|
vulns = query.all()
|
|
|
|
if not vulns:
|
|
return {"message": "Keine Vulnerabilities zum Korrigieren", "updated": 0}
|
|
|
|
# Sammle alle CVE-IDs für Vulnrichment-Abfrage
|
|
all_cve_ids = list(set(v.cve_id for v in vulns if v.cve_id and not v.cve_id.startswith("NESSUS-")))
|
|
|
|
if not all_cve_ids:
|
|
return {"message": "Keine echten CVEs zum Korrigieren", "updated": 0}
|
|
|
|
# Lade verifizierte Daten
|
|
verified_data = service.load_cisa_vulnrichment_data(all_cve_ids)
|
|
|
|
if not verified_data:
|
|
logger.warning("Keine verifizierten Daten von CISA Vulnrichment erhalten")
|
|
return {
|
|
"message": "CISA Vulnrichment-Daten nicht verfügbar",
|
|
"checked": len(vulns),
|
|
"updated": 0
|
|
}
|
|
|
|
# Gruppiere nach Asset
|
|
asset_groups: Dict[int, List[str]] = {}
|
|
for vuln in vulns:
|
|
if vuln.cve_id in verified_data:
|
|
if vuln.asset_id not in asset_groups:
|
|
asset_groups[vuln.asset_id] = []
|
|
asset_groups[vuln.asset_id].append(vuln.cve_id)
|
|
|
|
total_stats = {
|
|
"checked": 0,
|
|
"updated": 0,
|
|
"errors": 0,
|
|
"changes": []
|
|
}
|
|
|
|
for asset_id, cve_list in asset_groups.items():
|
|
asset_verified = {
|
|
cve_id: verified_data[cve_id]
|
|
for cve_id in cve_list
|
|
if cve_id in verified_data
|
|
}
|
|
|
|
stats = service.apply_overrides(asset_id, asset_verified, dry_run)
|
|
total_stats["checked"] += stats["checked"]
|
|
total_stats["updated"] += stats["updated"]
|
|
total_stats["errors"] += stats["errors"]
|
|
total_stats["changes"].extend(stats["changes"])
|
|
|
|
return total_stats |