
Ein quelloffener, einzelskriptbasierter CVE-Scanner für RMM-verwaltete Flotten. Reines PowerShell 7 – kombiniert Ihre RMM-Softwareinventur mit NVD, CISA KEV, EPSS und SSVC, um die Frage zu beantworten: Ist diese Version anfällig und wie dringend ist das Problem?
Ein Open-Source, Ein-Skript-CVE-Scanner für RMM-verwaltete Flotten. Reines PowerShell 7, keine Agents, keine Appliances, keine Lizenzgebühren.
Es beantwortet eine Frage für jede Software auf jedem von Ihnen verwalteten Endpunkt: ist die installierte Version bekanntermaßen verwundbar und wie dringend ist es? — indem es das Software-Inventar Ihres RMM mit kostenlosen, autoritativen Sicherheitsfeeds abgleicht:
Ausgaben: ein CSV-Bericht pro Gerät, eine SQLite-Historie mit SLA-Uhren und Woche-über-Woche-Änderungsereignissen, ein in sich geschlossenes HTML-Dashboard und ein optionaler Markdown-Expositionsbericht.
v1.0.0. Die Engine wurde sauber aus einem Scanner portiert, der wöchentlich in Produktion gegen eine Windows-Flotte lief (siehe docs/HISTORY.md). Vor dieser Veröffentlichung wurden beide gegen dieselbe Live-Flotte ausgeführt und zeilenweise verglichen: über Zehntausende von Funden, bei denen beide eine identische (device, software, version) sahen, stimmte jedes berechnete Feld — Status, CVSS, Schweregrad, KEV-Flag, SSVC-Entscheidung, Fix-Version — exakt überein.
Dieser Vergleich deckt die Urteilslogik ab. Er hat die Historie/SLA-Ebene nicht getestet und ersetzt nicht die Validierung des Tools in Ihrer eigenen Umgebung. Lesen Sie docs/known-limitations.md, bevor Sie ihm vertrauen — es ist ehrlich geschrieben, nicht schmeichelhaft.
Sie benötigen PowerShell 7 (pwsh). Zwei Möglichkeiten zur Ausführung:
cp config.example.json config.json
# edit config.json: fill ninjaone.client_id / client_secret / base_url,
# set output.report_dir, and (recommended) nvd.api_key
pwsh -File fleet-cve-scan.ps1
Exportieren Sie Ihr Inventar in eine CSV-Datei mit den Spalten hostname, software, version (optional device_id, os), dann:
cp config.example.json config.json # only output.report_dir is needed here
pwsh -File fleet-cve-scan.ps1 -InputCsv inventory.csv
-InputCsv führt null NinjaOne-Aufrufe durch — kein OAuth, keine API. Es fragt weiterhin NVD und die anderen öffentlichen Feeds ab, daher benötigen Sie weiterhin einen nvd.api_key in config.json und ein gültiges output.report_dir. Siehe docs/rmm-adapters.md für den Eingabevertrag und RMM-spezifische Exportrezepte.
brew install powershell; Windows: winget install Microsoft.PowerShell; Debian/Ubuntu: aus dem Microsoft-Paket-Repo installieren (packages.microsoft.com).brew install sqlite; Debian/Ubuntu: apt-get install sqlite3; Windows: das SQLite „Tools“-Bundle von sqlite.org herunterladen und sqlite3.exe in den PATH oder neben das Skript legen. Ohne es läuft der Scan trotzdem; first_seen fällt auf das Ausführungsdatum zurück.first_seen-Verfolgung. Fehlt es = diese Funktionen werden übersprungen, der Scan wird trotzdem abgeschlossen.epss.empiricalsecurity.com (dem EPSS-Bulk-Score-Host — FIRST hat das EPSS-Daten-Hosting dorthin verlegt), MSRC, endoflife.date und GitHub raw (CVE-Programm cvelistV5) — plus die NinjaOne API im Live-Modus.Die Konfiguration ist eine JSON-Datei (standardmäßig config.json neben dem Skript; Überschreibung mit -ConfigPath). Beginnen Sie mit config.example.json. Jeder Schlüssel, den der Scanner liest, sein Standardwert, wenn er weggelassen wird, und was er tut:
ninjaone (Live-Modus)nvd| Key | Standard | Zweck |
|---|---|---|
nvd.api_key | "" | NVD-API-Schlüssel, als apiKey-Header gesendet. Leer läuft unauthentifiziert, was die Standardwerte für nvd_rate_limit und nvd_min_spacing_ms auf die anonyme Obergrenze von NVD (4/30s, 6500ms) anstatt auf die authentifizierte (48/30s, 700ms) setzt. Kostenlos und ~10x schneller. |
nvd_min_spacing_ms, nvd_rate_limit und parallel_throttle werden vom Scanner gelesen, sind aber nicht in config.example.json enthalten — fügen Sie sie nur hinzu, wenn Sie anpassen müssen. Siehe docs/rate-limiting.md.
outputAlles unten landet in output.report_dir.
vuln-report-YYYY-MM-DD.csv — der Bericht pro Fund. 28 Spalten (Schema unten).vuln-history.db — SQLite-Historie (erfordert sqlite3): runs (Aggregate pro Durchlauf), findings (pro (device, software, cve) Historie offener Funde mit first_seen / last_seen / resolved_date / epss_score) und changes (getippte Woche-über-Woche-Ereignisse).cve-dashboard.html — in sich geschlossenes HTML-Dashboard (Diagrammbibliothek eingebettet, keine externen Anfragen). Optional nach output.dashboard_share kopiert.Der Spaltensatz ist fest und durch eine Gate-Prüfung erzwungen. In Reihenfolge:
Run-WeeklyScan.ps1 ist ein Wrapper für geplante Ausführungen: Es führt den Scanner aus seinem eigenen Verzeichnis aus, erfasst alle Ausgabeströme in einem datierten Log unter <script dir>/logs/ (überschreibbar mit -LogDir) und gibt den Exit-Code des Scanners weiter, sodass der Scheduler Fehler sieht.
Windows Task Scheduler:
schtasks /Create /TN "CVE-Scan-Weekly" ^
/TR "pwsh -NoProfile -File C:\path\to\Run-WeeklyScan.ps1" ^
/SC WEEKLY /D SUN /ST 22:00 /RU SYSTEM
cron (Linux/macOS):
0 22 * * 0 pwsh -File /path/to/Run-WeeklyScan.ps1
cve-dashboard.html ist vollständig in sich geschlossen — öffnen Sie es direkt, legen Sie es auf einem beliebigen statischen Host oder einer Dateifreigabe ab, oder kopieren Sie es automatisch bei jedem Durchlauf über output.dashboard_share. Für zugriffsgeschütztes Hosting enthält das dashboard-auth/-Verzeichnis einen minimalen ASP.NET-Host mit Microsoft Entra (Azure AD)-Authentifizierung; siehe dessen README und RUNBOOK.
-InputCsv-Modus erlaubt jedem RMM/SCCM/Intune-Export, sie zu füttern, aber nur der NinjaOne-Adapter wurde in Produktion eingesetzt.Die vollständige, ungeschönte Liste — die CPE-Überspringungsrate, Dedup-Key-Masking, Seite-1-totalResults, MSRC-Supersedence-Lücken und mehr — finden Sie in docs/known-limitations.md.
Einige strukturelle Entscheidungen, die unkonventionell aussehen, sind bewusst:
ForEach-Object -Parallel, und diese Runspaces können keine Funktionen im äußeren Gültigkeitsbereich sehen — weshalb einige Hilfsfunktionen absichtlich innerhalb des parallelen Blocks dupliziert sind. Ein Aufbau mit einer Funktion pro Datei würde diese Duplizierung nicht beseitigen (der parallele Körper müsste den Quellcode trotzdem pro Runspace injiziert bekommen); es würde nur einen Build-Schritt hinzufügen und das Ein-Datei-Bereitstellungsmodell brechen. Falls das Projekt jemals über eine einzelne Datei hinauswächst, ist die Paketierung die v2-förmige Änderung, kein Patch.[PSCustomObject]-Datensätze, keine Klassen. Funddatensätze werden innerhalb paralleler Runspaces erstellt und zurück zum Haupt-Runspace gemarshallt. PowerShell-Klasseninstanzen überleben diese Grenze nicht zuverlässig; [PSCustomObject] schon, und der CSV-Spaltenvertrag wird durch das Release-Gate anstelle eines Typsystems durchgesetzt.tests/ sind abhängigkeitsfreie Skripte mit Exit-Code-Überprüfungen, die überall dort ausgeführt werden können, wo pwsh existiert — einschließlich des Scan-Servers — ohne etwas zu installieren. CI führt sie alle auf Windows und Ubuntu aus.PRs willkommen — vor allem RMM-Adapter. Siehe CONTRIBUTING.md für die Grundregeln und die Anforderung der sicheren Richtung (Statuslogik darf nur in Richtung mehr Sichtbarkeit irren, niemals stillschweigend in Richtung PATCHED).
Dieses Tool ist nur so nützlich wie die kostenlosen, autoritativen Feeds, gegen die es abgleicht. Jeder der folgenden wird zur Scan-Zeit abgefragt; bitte respektieren Sie deren Bedingungen, wenn Sie darauf aufbauen.
cvelistV5 — CVE-Datensätze plus die SSVC/Vulnrichment-Daten, die vom CISA ADP beigesteuert wurden. CVE® ist eine eingetragene Marke der MITRE Corporation. https://github.com/CVEProject/cvelistV5Apache-2.0 — siehe LICENSE.
| Key | Standard | Zweck |
|---|
ninjaone.client_id | "" | NinjaOne API OAuth2-Client-ID. Erforderlich für Live-Scans; für -InputCsv leer lassen. |
ninjaone.client_secret | "" | NinjaOne API OAuth2-Client-Geheimnis. |
ninjaone.base_url | — (Beispiel liefert https://app.ninjarmm.com) | NinjaOne API Basis-URL; erforderlich für Live-Scans, kein eingebauter Fallback. Verwenden Sie den Host Ihrer Region (z.B. eu.ninjarmm.com, oc.ninjarmm.com). |
| Key | Standard | Zweck |
|---|
cvss_threshold | 7.0 | Minimaler CVSS-Basiswert, damit ein bewertetes CVE als VULNERABLE gemeldet wird. KEV-gelistete CVEs umgehen diese Untergrenze. |
ssvc_mission_prevalence | high | SSVC Mission Prevalence Stakeholder-Eingabe (low / medium / high). |
ssvc_public_wellbeing | high | SSVC Public Well-being Stakeholder-Eingabe (low / medium / high). |
| Key | Standard | Zweck |
|---|
nvd_cache_ttl_days | 7 | Tage, die ein NVD-Ergebnis (pro Softwarename) zwischengespeichert bleibt, bevor es erneut abgefragt wird. |
nvd_cache_flush_every | 500 | NVD-Cache nach jeweils N abgeschlossenen Elementen auf die Festplatte sichern, damit ein abgebrochener Scan seine Abrufe behält. 0 deaktiviert (Ende-des-Scans-Speicherung läuft trotzdem). |
nvd_min_spacing_ms | 700 mit API-Schlüssel, 6500 ohne | Mindestmillisekunden zwischen NVD-Aufrufen. Die bindende Ratenbeschränkung — zuerst anpassen. |
nvd_rate_limit | 48 mit API-Schlüssel, 4 ohne | Gleitfenster-Obergrenze für NVD-Aufrufe pro 30s (Absicherung). Mindestens 1. |
parallel_throttle | 20 | ForEach-Object -Parallel Runspace-Anzahl. NVD-Aufrufe werden trotzdem serialisiert; dies begrenzt nur die Cache-Treffer-Parallelität. Mindestens 1. |
kev_cache_ttl_hours | 24 | TTL für den zwischengespeicherten CISA KEV-Katalog. |
epss_cache_ttl_hours | 24 | TTL für die zwischengespeicherte FIRST.org EPSS-Score-Datei. |
eol_cache_ttl_days | 7 | TTL für zwischengespeicherte endoflife.date-Abfragen. |
msrc_cache_ttl_days | 30 | TTL für zwischengespeicherte MSRC CVRF-Daten. |
cvelist_cache_ttl_days | 7 | TTL für zwischengespeicherte CVE-Programm (cvelistV5) / CISA-ADP SSVC-Daten. |
cvelist_fetch_budget | 300 | Maximale CVE-Datensatzabrufe pro Durchlauf für SSVC/Vulnrichment-Anreicherung; der Rest wird auf den nächsten Durchlauf verschoben. |
| Key | Standard | Zweck |
|---|
epss_spike_threshold | 0.10 | Mindestanstieg des EPSS-Scores eines Fundes zwischen Scans, um ein EPSS_SPIKE-Änderungsereignis auszulösen. |
sla_days_critical | 14 | SLA-Fenster (Tage ab first_seen) für KRITISCHE Funde. |
sla_days_high | 30 | SLA-Fenster für andere bewertete Schweregrade. |
denied_software_csv | "" | Pfad zu einem optionalen CSV-Abgleich für verbotene Software. Leer deaktiviert die Prüfung. |
| Key | Standard | Zweck |
|---|
output.report_dir | (erforderlich) | Verzeichnis für alle Ausgaben — CSV, Historie-DB, Dashboard und Caches. |
output.wiki_security_page | "" | Optionaler Pfad zu einer Markdown-Expositionsseite. Nur geschrieben, wenn gesetzt. |
output.dashboard_share | "" | Optionaler Pfad/Freigabe, der bei jedem Durchlauf eine Kopie des Dashboards erhält. Leer = nur lokal. |
eol-report-YYYY-MM-DD.csv — geschrieben, wenn Familien am Ende ihrer Lebensdauer erkannt werden.denied-report-YYYY-MM-DD.csv — geschrieben, wenn denied_software_csv konfiguriert ist und zutrifft.nvd-cache.json, kev-cache.json, epss-cache.json, eol-cache.json, msrc-cache.json, cvelist-cache.json (alle gitignoriert).output.wiki_security_page, nur wenn gesetzt.| # | Spalte | Bedeutung |
|---|
| 1 | device_id | RMM-Geräte-ID (oder eine deterministische synthetische ID im -InputCsv-Modus). |
| 2 | hostname | Geräte-Hostname. |
| 3 | os | Betriebssystemzeichenfolge (kann leer sein). |
| 4 | software | Produktname wie inventarisiert. |
| 5 | installed_version | Versionszeichenfolge wie inventarisiert. |
| 6 | cve_id | CVE-Bezeichner. |
| 7 | cvss_score | CVSS-Basiswert (0 wenn keiner). |
| 8 | severity | CVSS-Schweregradbezeichnung. |
| 9 | cvss_version | Welcher CVSS-Standard den Wert erzeugt hat (4.0/3.1/3.0/2.0/leer). |
| 10 | epss_score | FIRST.org EPSS-Wahrscheinlichkeit (leer, wenn das CVE nicht im Katalog ist). |
| 11 | epss_percentile | EPSS-Perzentil (leer, wenn nicht vorhanden). |
| 12 | ssvc_exploitation | Rohe CISA SSVC-Ausnutzungsoption (none/poc/active; leer ohne ADP-Daten). |
| 13 | ssvc_decision | SSVC-Entscheidung Act/Attend/Track (leer ohne ADP-Daten). |
| 14 | status | VULNERABLE / PATCHED / UNKNOWN. |
| 15 | published_date | CVE-Veröffentlichungsdatum. |
| 16 | unknown_reason | Warum eine Zeile UNKNOWN ist (SUSPECT_VERSION / NO_VERSION_BOUND / VERSION_PARSE_ERROR; sonst leer). |
| 17 | is_kev | True, wenn das CVE im CISA KEV-Katalog ist. |
| 18 | kev_due_date | CISA KEV-Frist für Behebung. |
| 19 | kev_ransomware_use | KEV-Flag für Ransomware-Kampagnennutzung. |
| 20 | fix_version | Berechnete Fix-Grenzversion. |
| 21 | fix_boundary_inclusive | Ob die Fix-Grenze inklusiv ist. |
| 22 | advisory_url | Vendor-Advisory oder NVD-Detail-URL. |
| 23 | patch_evidence | Nachweis, wenn ein Microsoft-UNKNOWN auf PATCHED geändert wurde. |
| 24 | triage_status | Triage-Disposition aus triage.json (leer, wenn keine). |
| 25 | triage_note | Triage-Notiz. |
| 26 | first_seen | Datum, an dem der Fund erstmals beobachtet wurde (aus der Historie-DB). |
| 27 | sla_due | SLA-Fälligkeitsdatum. |
| 28 | sla_breach | Ob die SLA verletzt ist. |