
Un escáner de CVE de código abierto y un solo script para flotas gestionadas por RMM. Puramente PowerShell 7 — combina el inventario de software de tu RMM con NVD, CISA KEV, EPSS y SSVC para responder: ¿esta versión es vulnerable y qué tan urgente es?
Un escáner de CVE de código abierto y script único para flotas gestionadas por RMM. PowerShell 7 puro, sin agentes, sin dispositivos, sin tarifas de licencia.
Responde una pregunta por cada pieza de software en cada endpoint que gestionas: ¿la versión instalada se sabe vulnerable y qué tan urgente es? — al unir el inventario de software de tu RMM con fuentes de seguridad autorizadas y gratuitas:
Salidas: un informe CSV por dispositivo, un historial SQLite con relojes de SLA y eventos de cambio semana a semana, un panel HTML autónomo y un informe de exposición opcional en markdown.
v1.0.0. El motor fue portado en sala limpia desde un escáner que se ejecutaba semanalmente en producción contra una flota Windows (consulta docs/HISTORY.md). Antes de este lanzamiento, ambos se ejecutaron contra la misma flota en vivo y se compararon fila por fila: en decenas de miles de hallazgos donde ambos vieron un (dispositivo, software, versión) idéntico, cada campo calculado — estado, CVSS, severidad, bandera KEV, decisión SSVC, versión de corrección — coincidió exactamente.
Esa comparación cubre la lógica de veredicto. No ejercitó la capa de historial/SLA, y no sustituye la validación de la herramienta en tu propio entorno. Lee docs/known-limitations.md antes de confiar en ella — está escrita para ser honesta, no halagadora.
Necesitas PowerShell 7 (pwsh). Dos formas de ejecutarlo:
cp config.example.json config.json
# edita config.json: completa ninjaone.client_id / client_secret / base_url,
# establece output.report_dir y (recomendado) nvd.api_key
pwsh -File fleet-cve-scan.ps1
Exporta tu inventario a un CSV con las columnas hostname, software, version (opcional device_id, os), luego:
cp config.example.json config.json # aquí solo se necesita output.report_dir
pwsh -File fleet-cve-scan.ps1 -InputCsv inventory.csv
-InputCsv realiza cero llamadas a NinjaOne — sin OAuth, sin API. Aún consulta NVD y las otras fuentes públicas, por lo que igualmente querrás un nvd.api_key en config.json y un output.report_dir válido. Consulta docs/rmm-adapters.md para el contrato de entrada y recetas de exportación por RMM.
brew install powershell; Windows: winget install Microsoft.PowerShell; Debian/Ubuntu: instala desde el repositorio de paquetes de Microsoft (packages.microsoft.com).brew install sqlite; Debian/Ubuntu: apt-get install sqlite3; Windows: descarga el paquete "tools" de SQLite desde sqlite.org y coloca sqlite3.exe en PATH o junto al script. Sin él, el escaneo aún se ejecuta; first_seen retrocede a la fecha de ejecución.first_seen para SLA. Si está ausente, esas funciones se omiten, el escaneo aún se completa.epss.empiricalsecurity.com (el host de puntuación masiva de EPSS — FIRST movió el alojamiento de datos de EPSS allí), MSRC, endoflife.date y GitHub raw (cvelistV5 del programa CVE) — más la API de NinjaOne en modo en vivo.La configuración es un archivo JSON (por defecto config.json junto al script; se puede anular con -ConfigPath). Comienza desde config.example.json. Cada clave que el escáner lee, su valor predeterminado cuando se omite y qué hace:
ninjaone (modo en vivo)nvd| Clave | Predeterminado | Propósito |
|---|---|---|
nvd.api_key | "" | Clave API de NVD, enviada como encabezado apiKey. Si está en blanco, ejecuta sin autenticar, lo que reduce los valores predeterminados de nvd_rate_limit y nvd_min_spacing_ms al techo anónimo de NVD (4/30s, 6500ms) en lugar del autenticado (48/30s, 700ms). Gratuita y ~10 veces más rápida. |
nvd_min_spacing_ms, nvd_rate_limit y parallel_throttle son leídos por el escáner pero no están presentes en config.example.json — agrégalos solo si necesitas ajustarlos. Consulta docs/rate-limiting.md.
outputTodo lo siguiente se ubica en output.report_dir.
vuln-report-YYYY-MM-DD.csv — el informe por hallazgo. 28 columnas (esquema a continuación).vuln-history.db — historial SQLite (requiere sqlite3): runs (agregados por ejecución), findings (historial de hallazgos abiertos por (dispositivo, software, cve) con first_seen / last_seen / resolved_date / epss_score), y changes (eventos semana a semana tipificados).cve-dashboard.html — panel HTML autónomo (biblioteca de gráficos incrustada, sin solicitudes externas). Opcionalmente copiado a output.dashboard_share.El conjunto de columnas es fijo y se aplica mediante compuerta. En orden:
Run-WeeklyScan.ps1 es un envoltorio para ejecuciones programadas: ejecuta el escáner desde su propio directorio, captura todas las secuencias de salida en un registro fechado bajo <directorio del script>/logs/ (anular con -LogDir), y propaga el código de salida del escáner para que el programador vea los fallos.
Programador de tareas de Windows:
schtasks /Create /TN "CVE-Scan-Weekly" ^
/TR "pwsh -NoProfile -File C:\ruta\a\Run-WeeklyScan.ps1" ^
/SC WEEKLY /D SUN /ST 22:00 /RU SYSTEM
cron (Linux/macOS):
0 22 * * 0 pwsh -File /ruta/a/Run-WeeklyScan.ps1
cve-dashboard.html es completamente autónomo: ábrelo directamente, colócalo en cualquier host estático o recurso compartido, o cópialo automáticamente en cada ejecución mediante output.dashboard_share. Para alojamiento con control de acceso, el directorio dashboard-auth/ incluye un host ASP.NET mínimo con autenticación de Microsoft Entra (Azure AD); consulta su README y RUNBOOK.
-InputCsv permite que cualquier exportación de RMM/SCCM/Intune lo alimente, pero solo el adaptador de NinjaOne se ha ejecutado en producción.La lista completa y sin concesiones — la tasa de omisión de CPE, el enmascaramiento de claves de deduplicación, totalResults de la página 1, las brechas de supercesión de MSRC y más — está en docs/known-limitations.md.
Algunas opciones estructurales que parecen poco convencionales son deliberadas:
ForEach-Object -Parallel, y esos espacios de ejecución no pueden ver las funciones del ámbito externo, razón por la cual un puñado de ayudantes se duplican intencionalmente dentro del bloque paralelo. Un diseño de módulo con una función por archivo no eliminaría esa duplicación (el cuerpo paralelo aún necesitaría la fuente inyectada por espacio de ejecución); solo agregaría un paso de compilación y rompería el modelo de implementación de copiar un archivo. Si el proyecto alguna vez supera el archivo único, el empaquetado es el cambio con forma de v2, no un parche.[PSCustomObject], no clases. Los registros de hallazgos se producen dentro de espacios de ejecución paralelos y se devuelven al espacio de ejecución principal. Las instancias de clases de PowerShell no sobreviven de manera confiable ese límite; [PSCustomObject] sí lo hace, y el contrato de columnas CSV se aplica mediante la compuerta de lanzamiento en lugar de un sistema de tipos.tests/ son scripts sin dependencias con aserciones de código de salida, ejecutables en cualquier lugar donde exista pwsh — incluido el servidor de escaneo — sin nada que instalar. CI los ejecuta todos en Windows y Ubuntu.Se aceptan PRs — sobre todo adaptadores de RMM. Consulta CONTRIBUTING.md para las reglas básicas y el requisito de dirección segura (la lógica de estado solo puede errar hacia más visibilidad, nunca en silencio hacia PATCHED).
Esta herramienta solo es tan útil como las fuentes gratuitas y autorizadas con las que se une. Cada una de las siguientes se consulta en el momento del escaneo; respeta sus términos si construyes sobre esto.
cvelistV5 — registros CVE más los datos SSVC / vulnrichment aportados por el ADP de CISA. CVE® es una marca registrada de The MITRE Corporation. https://github.com/CVEProject/cvelistV5Apache-2.0 — consulta LICENSE.
| Clave | Predeterminado | Propósito |
|---|
ninjaone.client_id | "" | ID de cliente OAuth2 de la API de NinjaOne. Requerido para escaneos en vivo; déjalo en blanco para -InputCsv. |
ninjaone.client_secret | "" | Secreto de cliente OAuth2 de la API de NinjaOne. |
ninjaone.base_url | — (el ejemplo incluye https://app.ninjarmm.com) | URL base de la API de NinjaOne; requerido para escaneos en vivo, sin respaldo incorporado. Usa el host de tu región (ej. eu.ninjarmm.com, oc.ninjarmm.com). |
| Clave | Predeterminado | Propósito |
|---|
cvss_threshold | 7.0 | Puntuación base CVSS mínima para que un CVE con puntuación se reporte como VULNERABLE. Los CVE listados en KEV evitan este umbral. |
ssvc_mission_prevalence | high | Entrada de parte interesada de SSVC Mission Prevalence (low / medium / high). |
ssvc_public_wellbeing | high | Entrada de parte interesada de SSVC Public Well-being (low / medium / high). |
| Clave | Predeterminado | Propósito |
|---|
nvd_cache_ttl_days | 7 | Días que un resultado de NVD (por nombre de software) permanece en caché antes de volver a consultar. |
nvd_cache_flush_every | 500 | Guardar el caché de NVD en disco cada N elementos completados, para que un escaneo interrumpido conserve sus descargas. 0 lo desactiva (el guardado al final del escaneo aún se ejecuta). |
nvd_min_spacing_ms | 700 con clave API, 6500 sin ella | Milisegundos mínimos entre llamadas a NVD. La restricción de velocidad vinculante — ajústala primero. |
nvd_rate_limit | 48 con clave API, 4 sin ella | Límite de ventana deslizante en llamadas a NVD por 30s (un respaldo). Mínimo de 1. |
parallel_throttle | 20 | Recuento de espacios de ejecución de ForEach-Object -Parallel. Las llamadas a NVD se serializan independientemente; esto solo limita la concurrencia de aciertos en caché. Mínimo de 1. |
kev_cache_ttl_hours | 24 | TTL para el catálogo KEV de CISA en caché. |
epss_cache_ttl_hours | 24 | TTL para el archivo de puntuación EPSS de FIRST.org en caché. |
eol_cache_ttl_days | 7 | TTL para las consultas a endoflife.date en caché. |
msrc_cache_ttl_days | 30 | TTL para los datos CVRF de MSRC en caché. |
cvelist_cache_ttl_days | 7 | TTL para los datos SSVC del programa CVE (cvelistV5) / CISA-ADP en caché. |
cvelist_fetch_budget | 300 | Número máximo de descargas de registros CVE por ejecución para el enriquecimiento SSVC/vulnrichment; el resto se difiere a la siguiente ejecución. |
| Clave | Predeterminado | Propósito |
|---|
epss_spike_threshold | 0.10 | Aumento mínimo en la puntuación EPSS de un hallazgo entre escaneos para emitir un evento de cambio EPSS_SPIKE. |
sla_days_critical | 14 | Ventana de SLA (días desde first_seen) para hallazgos CRÍTICOS. |
sla_days_high | 30 | Ventana de SLA para otras severidades con puntuación. |
denied_software_csv | "" | Ruta a un CSV opcional de verificación cruzada de software denegado. Vacío desactiva la verificación. |
| Clave | Predeterminado | Propósito |
|---|
output.report_dir | (requerido) | Directorio para todas las salidas: CSV, base de datos de historial, panel y cachés. |
output.wiki_security_page | "" | Ruta opcional a una página de exposición en markdown. Se escribe solo cuando está configurada. |
output.dashboard_share | "" | Ruta/recurso compartido opcional que recibe una copia del panel en cada ejecución. Vacío = solo local. |
eol-report-YYYY-MM-DD.csv — se escribe cuando se detectan familias de software al final de su vida útil.denied-report-YYYY-MM-DD.csv — se escribe cuando denied_software_csv está configurado y coincide.nvd-cache.json, kev-cache.json, epss-cache.json, eol-cache.json, msrc-cache.json, cvelist-cache.json (todos ignorados por git).output.wiki_security_page, solo cuando está configurada.| # | Columna | Significado |
|---|
| 1 | device_id | ID del dispositivo en el RMM (o un ID sintético determinista en modo -InputCsv). |
| 2 | hostname | Nombre del host del dispositivo. |
| 3 | os | Cadena del sistema operativo (puede estar en blanco). |
| 4 | software | Nombre del producto según el inventario. |
| 5 | installed_version | Cadena de versión según el inventario. |
| 6 | cve_id | Identificador del CVE. |
| 7 | cvss_score | Puntuación base CVSS (0 si ninguna). |
| 8 | severity | Etiqueta de severidad CVSS. |
| 9 | cvss_version | Qué estándar CVSS produjo la puntuación (4.0/3.1/3.0/2.0/en blanco). |
| 10 | epss_score | Probabilidad EPSS de FIRST.org (en blanco si el CVE está ausente del catálogo). |
| 11 | epss_percentile | Percentil EPSS (en blanco si ausente). |
| 12 | ssvc_exploitation | Opción de explotación SSVC de CISA en bruto (none/poc/active; en blanco sin datos de ADP). |
| 13 | ssvc_decision | Decisión SSVC Act/Attend/Track (en blanco sin datos de ADP). |
| 14 | status | VULNERABLE / PATCHED / UNKNOWN. |
| 15 | published_date | Fecha de publicación del CVE. |
| 16 | unknown_reason | Por qué una fila es UNKNOWN (SUSPECT_VERSION / NO_VERSION_BOUND / VERSION_PARSE_ERROR; en blanco en caso contrario). |
| 17 | is_kev | True cuando el CVE está en el catálogo KEV de CISA. |
| 18 | kev_due_date | Fecha de vencimiento de corrección KEV de CISA. |
| 19 | kev_ransomware_use | Bandera de uso en campañas de ransomware KEV. |
| 20 | fix_version | Versión de límite de corrección calculada. |
| 21 | fix_boundary_inclusive | Si el límite de corrección es inclusivo. |
| 22 | advisory_url | URL del aviso del proveedor o de detalles de NVD. |
| 23 | patch_evidence | Evidencia cuando un UNKNOWN de Microsoft se cambió a PATCHED. |
| 24 | triage_status | Disposición de triaje desde triage.json (en blanco si ninguna). |
| 25 | triage_note | Nota de triaje. |
| 26 | first_seen | Fecha en que se observó por primera vez el hallazgo (desde la base de datos de historial). |
| 27 | sla_due | Fecha de vencimiento del SLA. |
| 28 | sla_breach | Si se ha incumplido el SLA. |