
fleet-cve-scanner v1.1.0
Un scanner de CVE open-source et à script unique pour les flottes gérées par RMM. PowerShell 7 pur — croise votre inventaire logiciel RMM avec NVD, CISA KEV, EPSS et SSVC pour répondre à la question : cette version est-elle vulnérable, et quelle est son urgence ?
fleet-cve-scanner
Un scanner CVE open-source, mono-script pour les parcs gérés par RMM. PowerShell 7 pur, sans agents, sans appliances, sans frais de licence.
Il répond à une question pour chaque logiciel sur chaque poste que vous gérez : la version installée est-elle connue comme vulnérable, et quelle est son urgence ? — en croisant l'inventaire logiciel de votre RMM avec des flux de sécurité gratuits et reconnus :
- NIST NVD — CVEs connues + plages de versions affectées
- CISA KEV — activement exploitées dans la nature (passe outre le seuil de score)
- EPSS — probabilité d'exploitation dans les 30 prochains jours
- CISA SSVC — décisions de priorisation act / attend / track
- MSRC — preuve qu'un correctif Microsoft couvre déjà la constatation
- endoflife.date — logiciels qui ne seront plus jamais corrigés
Sorties : un rapport CSV par appareil, un historique SQLite avec des horloges SLA et des événements de changement de semaine en semaine, un tableau de bord HTML autonome, et un rapport d'exposition optionnel au format markdown.
Statut
v1.0.0. Le moteur a été porté en chambre blanche à partir d'un scanner qui tournait chaque semaine en production sur un parc Windows (voir docs/HISTORY.md). Avant cette version, les deux ont été exécutés sur le même parc en direct et comparés ligne par ligne : sur des dizaines de milliers de constatations où les deux voyaient un (appareil, logiciel, version) identique, chaque champ calculé — statut, CVSS, sévérité, indicateur KEV, décision SSVC, version de correctif — correspondait exactement.
Cette comparaison couvre la logique de verdict. Elle n'a pas mis en œuvre la couche historique/SLA, et ne remplace pas une validation de l'outil dans votre propre environnement. Lisez docs/known-limitations.md avant de lui faire confiance — il est écrit pour être honnête plutôt que flatteur.
Démarrage rapide
Vous avez besoin de PowerShell 7 (pwsh). Deux façons de l'exécuter :
A. Scan NinjaOne en direct
cp config.example.json config.json
# éditez config.json : remplissez ninjaone.client_id / client_secret / base_url,
# définissez output.report_dir, et (recommandé) nvd.api_key
pwsh -File fleet-cve-scan.ps1
B. Hors ligne — tout RMM, SCCM ou Intune (sans accès API)
Exportez votre inventaire vers un CSV avec les colonnes hostname, software, version (optionnel device_id, os), puis :
cp config.example.json config.json # seul output.report_dir est nécessaire ici
pwsh -File fleet-cve-scan.ps1 -InputCsv inventory.csv
-InputCsv fait zéro appel NinjaOne — pas d'OAuth, pas d'API. Il interroge toujours NVD et les autres flux publics, donc vous voulez toujours un nvd.api_key dans config.json et un output.report_dir valide. Voir docs/rmm-adapters.md pour le contrat d'entrée et les recettes d'export par RMM.
Installation des prérequis
- PowerShell 7 — macOS :
brew install powershell; Windows :winget install Microsoft.PowerShell; Debian/Ubuntu : installer depuis le dépôt de paquets Microsoft (packages.microsoft.com). - sqlite3 (optionnel, alimente l'historique/les tendances) — macOS : préinstallé ou
brew install sqlite; Debian/Ubuntu :apt-get install sqlite3; Windows : télécharger le bundle « tools » SQLite depuis sqlite.org et placersqlite3.exesur lePATHou à côté du script. Sans lui, le scan s'exécute toujours ;first_seentombe à la date d'exécution.
Prérequis
- PowerShell 7 (7.4+ recommandé). Le scan parallèle nécessite 7.0+.
- sqlite3 — optionnel ; active la base de données d'historique, les tendances et le suivi SLA
first_seen. Absent = ces fonctionnalités sont ignorées, le scan se termine quand même. - Sortie réseau vers NVD, CISA (KEV),
epss.empiricalsecurity.com(l'hôte de notation en masse EPSS — FIRST a déplacé l'hébergement des données EPSS là-bas), MSRC, endoflife.date, et GitHub raw (CVE ProgramcvelistV5) — plus l'API NinjaOne en mode direct. - Identifiants — le mode direct nécessite un identifiant/secret client OAuth2 NinjaOne ; le mode hors ligne nécessite uniquement un CSV d'inventaire. Une clé API NVD est techniquement optionnelle mais effectivement requise à l'échelle d'un parc : NVD autorise 50 requêtes/30s avec une clé et seulement 5/30s sans. Le scanner détecte celle que vous avez et s'adapte en conséquence, donc une exécution sans clé est correcte mais environ 10 fois plus lente — acceptable pour une poignée de produits, impraticable pour un vrai parc. Les clés sont gratuites.
Référence de configuration
La configuration est un fichier JSON (par défaut config.json à côté du script ; remplacez par -ConfigPath). Commencez par config.example.json. Chaque clé lue par le scanner, sa valeur par défaut lorsqu'omise, et ce qu'elle fait :
ninjaone (mode direct)
| Clé | Défaut | Objectif |
|---|---|---|
ninjaone.client_id | "" | Identifiant client OAuth2 de l'API NinjaOne. Requis pour les scans en direct ; laissez vide pour -InputCsv. |
ninjaone.client_secret | "" | Secret client OAuth2 de l'API NinjaOne. |
ninjaone.base_url | — (l'exemple fournit https://app.ninjarmm.com) | URL de base de l'API NinjaOne ; requise pour les scans en direct, aucun repli intégré. Utilisez l'hôte de votre région (par ex. eu.ninjarmm.com, oc.ninjarmm.com). |
nvd
| Clé | Défaut | Objectif |
|---|---|---|
nvd.api_key | "" | Clé API NVD, envoyée comme en-tête apiKey. Vide exécute une session non authentifiée, ce qui abaisse les valeurs par défaut de nvd_rate_limit et nvd_min_spacing_ms au plafond anonyme de NVD (4/30s, 6500ms) au lieu du plafond authentifié (48/30s, 700ms). Gratuit et ~10 fois plus rapide. |
Scan et notation
| Clé | Défaut | Objectif |
|---|---|---|
cvss_threshold | 7.0 | Score de base CVSS minimal pour qu'une CVE notée soit signalée comme VULNÉRABLE. Les CVE listées dans KEV contournent ce seuil. |
ssvc_mission_prevalence | high | Entrée partie prenante de la prévalence de mission SSVC (low / medium / high). |
ssvc_public_wellbeing | high | Entrée partie prenante du bien-être public SSVC (low / medium / high). |
Mise en cache et limitation de débit
| Clé | Défaut | Objectif |
|---|---|---|
nvd_cache_ttl_days | 7 | Jours pendant lesquels un résultat NVD (par nom de logiciel) reste en cache avant une nouvelle requête. |
nvd_cache_flush_every | 500 | Point de sauvegarde du cache NVD sur disque tous les N éléments terminés, afin qu'un scan interrompu conserve ses récupérations. 0 désactive (la sauvegarde de fin de scan s'exécute toujours). |
nvd_min_spacing_ms | 700 avec une clé API, 6500 sans | Millisecondes minimales entre les appels NVD. La contrainte de débit contraignante — ajustez cela en premier. |
nvd_rate_limit | 48 avec une clé API, 4 sans | Limite glissante sur les appels NVD par 30s (une protection). Minimum de 1. |
parallel_throttle | 20 | Nombre de runspaces ForEach-Object -Parallel. Les appels NVD sont de toute façon sérialisés ; cela ne limite que la concurrence des accès au cache. Minimum de 1. |
kev_cache_ttl_hours | 24 | TTL pour le catalogue KEV CISA mis en cache. |
epss_cache_ttl_hours | 24 | TTL pour le fichier de scores EPSS de FIRST.org mis en cache. |
eol_cache_ttl_days | 7 | TTL pour les consultations endoflife.date mises en cache. |
msrc_cache_ttl_days | 30 | TTL pour les données CVRF MSRC mises en cache. |
cvelist_cache_ttl_days | 7 | TTL pour les données SSVC CVE Program (cvelistV5) / CISA-ADP mises en cache. |
cvelist_fetch_budget | 300 | Nombre maximal de récupérations d'enregistrements CVE par exécution pour l'enrichissement SSVC/vulnrichment ; le reste est reporté à l'exécution suivante. |
nvd_min_spacing_ms, nvd_rate_limit, et parallel_throttle sont lus par le scanner mais ne sont pas présents dans config.example.json — ajoutez-les uniquement si vous devez les ajuster. Voir docs/rate-limiting.md.
Enrichissement et SLA
| Clé | Défaut | Objectif |
|---|---|---|
epss_spike_threshold | 0.10 | Augmentation minimale du score EPSS d'une constatation entre les scans pour émettre un événement de changement EPSS_SPIKE. |
sla_days_critical | 14 | Fenêtre SLA (jours depuis first_seen) pour les constatations CRITIQUES. |
sla_days_high | 30 | Fenêtre SLA pour les autres sévérités notées. |
denied_software_csv | "" | Chemin vers un CSV optionnel de vérification croisée des logiciels interdits. Vide désactive la vérification. |
output
| Clé | Défaut | Objectif |
|---|---|---|
output.report_dir | (obligatoire) | Répertoire pour toutes les sorties — CSV, base de données d'historique, tableau de bord et caches. |
output.wiki_security_page | "" | Chemin optionnel vers une page d'exposition au format markdown. Écrite uniquement lorsqu'elle est définie. |
output.dashboard_share | "" | Chemin/partage optionnel qui reçoit une copie du tableau de bord à chaque exécution. Vide = local uniquement. |
Sorties
Tout ce qui suit atterrit dans output.report_dir.
vuln-report-YYYY-MM-DD.csv— le rapport par constatation. 28 colonnes (schéma ci-dessous).vuln-history.db— historique SQLite (nécessitesqlite3) :runs(agrégats par exécution),findings(historique des constatations ouvertes par(device, software, cve)avecfirst_seen/last_seen/resolved_date/epss_score), etchanges(événements typés de semaine en semaine).cve-dashboard.html— tableau de bord HTML autonome (bibliothèque de graphiques intégrée, aucune requête externe). Optionnellement copié versoutput.dashboard_share.eol-report-YYYY-MM-DD.csv— écrit lorsque des familles en fin de vie sont détectées.denied-report-YYYY-MM-DD.csv— écrit lorsquedenied_software_csvest configuré et correspond.- Fichiers de cache —
nvd-cache.json,kev-cache.json,epss-cache.json,eol-cache.json,msrc-cache.json,cvelist-cache.json(tous ignorés par git). - Page d'exposition Markdown — à
output.wiki_security_page, uniquement lorsque défini.
Schéma CSV (28 colonnes)
L'ensemble des colonnes est fixe et validé par une passerelle. Dans l'ordre :
| # | Colonne | Signification |
|---|---|---|
| 1 | device_id | Identifiant d'appareil RMM (ou un identifiant synthétique déterministe en mode -InputCsv). |
| 2 | hostname | Nom d'hôte de l'appareil. |
| 3 | os | Chaîne du système d'exploitation (peut être vide). |
| 4 | software | Nom du produit tel qu'inventorié. |
| 5 | installed_version | Chaîne de version telle qu'inventoriée. |
| 6 | cve_id | Identifiant CVE. |
| 7 | cvss_score | Score de base CVSS (0 si aucun). |
| 8 | severity | Étiquette de sévérité CVSS. |
| 9 | cvss_version | Quelle norme CVSS a produit le score (4.0/3.1/3.0/2.0/vide). |
| 10 | epss_score | Probabilité FIRST.org EPSS (vide si la CVE est absente du catalogue). |
| 11 | epss_percentile | Centile EPSS (vide si absent). |
| 12 | ssvc_exploitation | Option d'exploitation SSVC brute de CISA (none/poc/active ; vide sans données ADP). |
| 13 | ssvc_decision | Décision SSVC Act/Attend/Track (vide sans données ADP). |
| 14 | status | VULNERABLE / PATCHED / UNKNOWN. |
| 15 | published_date | Date de publication de la CVE. |
| 16 | unknown_reason | Pourquoi une ligne est UNKNOWN (SUSPECT_VERSION / NO_VERSION_BOUND / VERSION_PARSE_ERROR ; vide sinon). |
| 17 | is_kev | True lorsque la CVE figure dans le catalogue KEV de CISA. |
| 18 | kev_due_date | Date limite de correction KEV CISA. |
| 19 | kev_ransomware_use | Indicateur d'utilisation de campagne de ransomware KEV. |
| 20 | fix_version | Version de la limite de correctif calculée. |
| 21 | fix_boundary_inclusive | Si la limite de correctif est inclusive. |
| 22 | advisory_url | URL de l'avis du fournisseur ou des détails NVD. |
| 23 | patch_evidence | Preuve lorsqu'un UNKNOWN Microsoft a été basculé en PATCHED. |
| 24 | triage_status | Disposition de triage depuis triage.json (vide si aucun). |
| 25 | triage_note | Note de triage. |
| 26 | first_seen | Date à laquelle la constatation a été observée pour la première fois (depuis la base de données d'historique). |
| 27 | sla_due | Date d'échéance SLA. |
| 28 | sla_breach | Si le SLA est enfreint. |
Planification
Run-WeeklyScan.ps1 est un wrapper pour les exécutions planifiées : il exécute le scanner depuis son propre répertoire, capture tous les flux de sortie vers un journal daté sous <répertoire script>/logs/ (remplacez par -LogDir), et propage le code de sortie du scanner afin que le planificateur voie les échecs.
Planificateur de tâches Windows :
schtasks /Create /TN "CVE-Scan-Weekly" ^
/TR "pwsh -NoProfile -File C:\chemin\vers\Run-WeeklyScan.ps1" ^
/SC WEEKLY /D SUN /ST 22:00 /RU SYSTEM
cron (Linux/macOS) :
0 22 * * 0 pwsh -File /chemin/vers/Run-WeeklyScan.ps1
Hébergement du tableau de bord
cve-dashboard.html est entièrement autonome — ouvrez-le directement, déposez-le sur n'importe quel hôte statique ou partage de fichiers, ou copiez-le automatiquement à chaque exécution via output.dashboard_share. Pour un hébergement à accès contrôlé, le répertoire dashboard-auth/ fournit un hôte ASP.NET minimal avec authentification Microsoft Entra (Azure AD) ; consultez son README et son RUNBOOK.
Limitations honnêtes (à lire avant de lui faire confiance)
- La correspondance CPE est floue par nature. 40 à 60 % de l'inventaire typique n'a aucune entrée NVD et est ignoré (journalisé, jamais silencieusement). La sélection de CPE basée sur le nom peut choisir le mauvais produit ; un workflow de suppression existe parce que c'est nécessaire. C'est le plafond de l'approche NVD gratuite — un scanner payant avec un catalogue de détection organisé sera plus précis. La valeur de cet outil est d'être gratuit, transparent et vérifiable.
- UNKNOWN est un verdict de première classe. Lorsque le scanner ne peut pas prouver vulnérable-ou-corrigé, il le dit au lieu de deviner.
- Vérifié seulement avec NinjaOne. Le moteur de scan est indépendant du RMM (l'entrée est simplement appareil + logiciel + version) ; le mode
-InputCsvpermet à n'importe quel export RMM/SCCM/Intune de l'alimenter, mais seul l'adaptateur NinjaOne a été exécuté en production.
La liste complète et sans fard — le taux de saut CPE, le masquage des clés de déduplication, totalResults page 1, les lacunes de remplacement MSRC, et plus encore — se trouve dans docs/known-limitations.md.
Notes de conception
Quelques choix structurels qui semblent non conventionnels sont délibérés :
- Fichier unique, pas un module. Le chemin critique du scan s'exécute à l'intérieur de
ForEach-Object -Parallel, et ces runspaces ne peuvent pas voir les fonctions de la portée externe — c'est pourquoi quelques aides sont intentionnellement dupliquées à l'intérieur du bloc parallèle. Une disposition de module avec une fonction par fichier ne supprimerait pas cette duplication (le corps parallèle aurait toujours besoin de la source injectée par runspace) ; elle ajouterait simplement une étape de construction et casserait le modèle de déploiement à un seul fichier. Si le projet dépasse un jour le fichier unique, l'empaquetage est un changement de forme v2, pas un correctif. - Enregistrements
[PSCustomObject], pas de classes. Les enregistrements de constatations sont produits à l'intérieur des runspaces parallèles et renvoyés au runspace principal. Les instances de classes PowerShell ne survivent pas de manière fiable à cette frontière ;[PSCustomObject]le fait, et le contrat de colonnes CSV est appliqué par la passerelle de version plutôt que par un système de types. - Scripts de test PS7 simples, pas Pester. Les suites dans
tests/sont des scripts sans dépendances avec des assertions de code de sortie, exécutables partout oùpwshexiste — y compris le serveur de scan — sans rien à installer. CI les exécute tous sur Windows et Ubuntu.
Documentation
- docs/how-it-works.md — le pipeline de scan étape par étape, la logique de verdict, la discipline de cache et le contrat de défaillance douce.
- docs/known-limitations.md — toutes les manières connues par lesquelles le scanner peut se tromper, et pourquoi.
- docs/rate-limiting.md — les leçons sur la limitation de débit NVD sous forme de guide d'exploitation et de table d'ajustement.
- docs/rmm-adapters.md — le contrat d'entrée, les deux chemins d'inventaire intégrés et comment ajouter un adaptateur natif.
- docs/HISTORY.md — historique de version condensé et anonymisé et les leçons d'ingénierie derrière chaque version.
Contribuer
Les PRs sont les bienvenues — les adaptateurs RMM avant tout. Voir CONTRIBUTING.md pour les règles de base et l'exigence de direction sûre (la logique de statut ne peut errer que vers plus de visibilité, jamais silencieusement vers PATCHED).
Sources de données & attribution
Cet outil n'est aussi utile que les flux gratuits et reconnus qu'il croise. Chacun des suivants est interrogé au moment du scan ; veuillez respecter leurs conditions si vous construisez dessus.
- NIST National Vulnerability Database (NVD) — enregistrements CVE et plages de versions affectées, via l'API NVD. Ce produit utilise l'API NVD mais n'est ni approuvé ni certifié par la NVD. https://nvd.nist.gov/
- Catalogue des vulnérabilités exploitées connues (KEV) de CISA — la liste faisant autorité des CVE connues pour être exploitées dans la nature. https://www.cisa.gov/known-exploited-vulnerabilities-catalog
- CVE Program —
cvelistV5— enregistrements CVE plus les données SSVC / vulnrichment fournies par l'ADP de CISA. CVE® est une marque déposée de The MITRE Corporation. https://github.com/CVEProject/cvelistV5 - SSVC (Stakeholder-Specific Vulnerability Categorization) — la logique de décision act / attend / track dans cet outil est dérivée de CERT/CC SSVC (Carnegie Mellon University Software Engineering Institute) et de l'arbre de décision du coordinateur SSVC de CISA, version 2.0.3. Le crédit à CERT/CC et CISA est requis. https://www.cisa.gov/ssvc · https://github.com/CERTCC/SSVC
- FIRST.org EPSS (Exploit Prediction Scoring System) — probabilité d'exploitation dans les 30 prochains jours. Les données EPSS sont fournies par FIRST. Voir Jay Jacobs, Sasha Romanosky, Benjamin Edwards, Michael Roytman, Idris Adjerid (2021), Exploit Prediction Scoring System, Digital Threats: Research and Practice, 2(3). https://www.first.org/epss/
- Microsoft Security Response Center (MSRC) — l'API CVRF, utilisée pour confirmer quand une mise à jour de sécurité Microsoft couvre déjà une constatation. © Microsoft Corporation. https://api.msrc.microsoft.com/ · https://msrc.microsoft.com/
- endoflife.date — dates de fin de vie et de fin de support pour les logiciels qui ne seront plus jamais corrigés. Données gracieuseté du projet endoflife.date. https://endoflife.date/
Licence
Apache-2.0 — voir LICENSE.