
Открытый сканер CVE с одним скриптом для парков управляемых через RMM. Чистый PowerShell 7 — сопоставляет инвентарь программного обеспечения вашего RMM с NVD, CISA KEV, EPSS и SSVC, чтобы ответить: уязвима ли эта версия и насколько это срочно?
# fleet-cve-scanner Сканер CVE с открытым исходным кодом для флотов, управляемых через RMM. Один скрипт на чистом PowerShell 7, без агентов, без серверных компонентов, без лицензионных отчислений. Он отвечает на один вопрос для каждого программного обеспечения на каждом управляемом устройстве: **известно ли, что установленная версия уязвима, и насколько это срочно?** — объединяя инвентаризацию ПО вашего RMM с бесплатными авторитетными источниками безопасности: - **NIST NVD** — известные CVE + затронутые версионные диапазоны - **CISA KEV** — активно эксплуатируемые в реальных атаках (обходит порог оценки) - **EPSS** — вероятность эксплуатации в ближайшие 30 дней - **CISA SSVC** — решения о приоритете: действовать / обратить внимание / отслеживать - **MSRC** — подтверждение, что патч Microsoft уже покрывает находку - **endoflife.date** — ПО, для которого больше не будет патчей Результаты: отчёт CSV по каждому устройству, история SQLite с часами SLA и событиями изменений неделя к неделе, самодостаточная HTML-панель управления и опциональный отчёт об уязвимостях в формате Markdown. ## Статус **v1.0.0.** Движок был перенесён «изолированно» со сканера, который еженедельно выполнялся в продакшене для Windows-флота (см. [docs/HISTORY.md](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/docs/HISTORY.md)). Перед этим релизом оба сканера запускались на одном и том же живом флоте и сравнивались построчно: по десяткам тысяч находок, где оба видели одинаковую тройку *(устройство, ПО, версия)*, каждое вычисленное поле — статус, CVSS, уровень серьёзности, флаг KEV, решение SSVC, версия исправления — полностью совпало. Это сравнение покрывает логику **вердикта**. Оно не тестировало слой истории/SLA и не заменяет проверку инструмента в вашем окружении. Прочтите [docs/known-limitations.md](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/docs/known-limitations.md), прежде чем доверять ему — он написан честно, а не лестно. ## Быстрый старт Вам понадобится [PowerShell 7](#требования) (`pwsh`). Два способа запуска: ### A. Живое сканирование через NinjaOne ```bash cp config.example.json config.json # отредактируйте config.json: укажите ninjaone.client_id / client_secret / base_url, # установите output.report_dir и (рекомендуется) nvd.api_key pwsh -File fleet-cve-scan.ps1 ``` ### B. Офлайн — любой RMM, SCCM или Intune (без доступа к API) Экспортируйте инвентаризацию в CSV с колонками `hostname, software, version` (опционально `device_id, os`), затем: ```bash cp config.example.json config.json # здесь нужен только output.report_dir pwsh -File fleet-cve-scan.ps1 -InputCsv inventory.csv ``` `-InputCsv` не выполняет **ни одного** вызова NinjaOne — никакого OAuth, никакого API. Он всё равно запрашивает NVD и другие публичные каналы, поэтому вам также понадобится `nvd.api_key` в `config.json` и корректный `output.report_dir`. См. [docs/rmm-adapters.md](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/docs/rmm-adapters.md) для описания контракта входных данных и инструкций по экспорту для каждого RMM. ### Установка предварительных компонентов - **PowerShell 7** — macOS: `brew install powershell`; Windows: `winget install Microsoft.PowerShell`; Debian/Ubuntu: установка из репозитория Microsoft (`packages.microsoft.com`). - **sqlite3** (опционально, для истории/трендов) — macOS: предустановлен или `brew install sqlite`; Debian/Ubuntu: `apt-get install sqlite3`; Windows: скачайте пакет «tools» SQLite с sqlite.org и поместите `sqlite3.exe` в `PATH` или рядом со скриптом. Без него сканирование всё равно выполняется; `first_seen` будет установлен на дату запуска. ## Требования - **PowerShell 7** (рекомендуется 7.4+). Параллельное сканирование требует 7.0+. - **sqlite3** — опционально; включает базу данных истории, тренды и отслеживание `first_seen` для SLA. Отсутствует = эти функции пропускаются, сканирование завершается. - **Исходящий сетевой трафик** к NVD, CISA (KEV), `epss.empiricalsecurity.com` (хост массовых оценок EPSS — FIRST переместил хостинг данных EPSS туда), MSRC, endoflife.date и GitHub raw (CVE Program `cvelistV5`) — плюс API NinjaOne в живом режиме. - **Учётные данные** — живому режиму требуется OAuth2-идентификатор/секрет клиента NinjaOne; офлайн-режиму нужен только CSV инвентаризации. API-ключ NVD технически не обязателен, но **фактически требуется для масштаба флота**: NVD разрешает 50 запросов за 30 секунд с ключом и только 5 за 30 секунд без него. Сканер определяет, какой у вас ключ, и подстраивает троттлинг, так что запуск без ключа корректен, но примерно в 10 раз медленнее — нормально для нескольких продуктов, но непрактично для реального флота. [Ключи бесплатны.](https://nvd.nist.gov/developers/request-an-api-key) ## Справочник по конфигурации Конфигурация — это JSON-файл (по умолчанию `config.json` рядом со скриптом; можно переопределить с помощью `-ConfigPath`). Начните с [`config.example.json`](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/config.example.json). Все ключи, которые читает сканер, значения по умолчанию при их отсутствии и что они делают: ### `ninjaone` (живой режим) | Ключ | По умолчанию | Назначение | | --- | --- | --- | | `ninjaone.client_id` | `""` | OAuth2-идентификатор клиента API NinjaOne. Обязателен для живого сканирования; оставьте пустым для `-InputCsv`. | | `ninjaone.client_secret` | `""` | Секрет клиента API NinjaOne. | | `ninjaone.base_url` | — (в примере указано `https://app.ninjarmm.com`) | Базовый URL API NinjaOne; обязателен для живого сканирования, встроенного запасного варианта нет. Используйте хост вашего региона (например, `eu.ninjarmm.com`, `oc.ninjarmm.com`). | ### `nvd` | Ключ | По умолчанию | Назначение | | --- | --- | --- | | `nvd.api_key` | `""` | API-ключ NVD, отправляется как заголовок `apiKey`. Пустое значение — неаутентифицированный доступ, который понижает значения по умолчанию для `nvd_rate_limit` и `nvd_min_spacing_ms` до анонимных лимитов NVD (4/30с, 6500 мс) вместо аутентифицированных (48/30с, 700 мс). Бесплатный и ~в 10 раз быстрее. | ### Сканирование и оценка | Ключ | По умолчанию | Назначение | | --- | --- | --- | | `cvss_threshold` | `7.0` | Минимальная базовая оценка CVSS для того, чтобы CVE с оценкой считался УЯЗВИМЫМ. CVE из списка KEV обходят этот порог. | | `ssvc_mission_prevalence` | `high` | Входные данные заинтересованной стороны для Mission Prevalence в SSVC (`low` / `medium` / `high`). | | `ssvc_public_wellbeing` | `high` | Входные данные заинтересованной стороны для Public Well-being в SSVC (`low` / `medium` / `high`). | ### Кэширование и ограничение скорости | Ключ | По умолчанию | Назначение | | --- | --- | --- | | `nvd_cache_ttl_days` | `7` | Дни, в течение которых результат NVD (на одно имя ПО) хранится в кэше перед повторным запросом. | | `nvd_cache_flush_every` | `500` | Сохранять кэш NVD на диск каждые N обработанных элементов, чтобы прерванное сканирование сохранило загруженные данные. `0` отключает (сохранение по окончании сканирования всё равно выполняется). | | `nvd_min_spacing_ms` | `700` с API-ключом, `6500` без | Минимальное количество миллисекунд между вызовами NVD. **Основное ограничение скорости** — настраивайте его в первую очередь. | | `nvd_rate_limit` | `48` с API-ключом, `4` без | Ограничение по скользящему окну на количество вызовов NVD за 30 секунд (запасной лимит). Минимум 1. | | `parallel_throttle` | `20` | Количество пространств выполнения в `ForEach-Object -Parallel`. Вызовы NVD в любом случае последовательны; это ограничивает только параллелизм при попадании в кэш. Минимум 1. | | `kev_cache_ttl_hours` | `24` | Время жизни кэша каталога CISA KEV. | | `epss_cache_ttl_hours` | `24` | Время жизни кэша файла оценок EPSS от FIRST.org. | | `eol_cache_ttl_days` | `7` | Время жизни кэша запросов к endoflife.date. | | `msrc_cache_ttl_days` | `30` | Время жизни кэша данных MSRC CVRF. | | `cvelist_cache_ttl_days` | `7` | Время жизни кэша данных CVE Program (`cvelistV5`) / CISA-ADP SSVC. | | `cvelist_fetch_budget` | `300` | Максимальное количество загрузок записей CVE за один запуск для обогащения SSVC/vulnrichment; остальные откладываются до следующего запуска. | Значения `nvd_min_spacing_ms`, `nvd_rate_limit` и `parallel_throttle` считываются сканером, но отсутствуют в `config.example.json` — добавляйте их только если нужно настроить. См. [docs/rate-limiting.md](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/docs/rate-limiting.md). ### Обогащение и SLA | Ключ | По умолчанию | Назначение | | --- | --- | --- | | `epss_spike_threshold` | `0.10` | Минимальный рост оценки EPSS у находки между сканированиями, чтобы создать событие изменения `EPSS_SPIKE`. | | `sla_days_critical` | `14` | Окно SLA (дней с `first_seen`) для находок CRITICAL. | | `sla_days_high` | `30` | Окно SLA для других оценок серьёзности. | | `denied_software_csv` | `""` | Путь к опциональному CSV-файлу с запрещённым ПО для перекрёстной проверки. Пустое значение отключает проверку. | ### `output` | Ключ | По умолчанию | Назначение | | --- | --- | --- | | `output.report_dir` | *(обязательно)* | Каталог для всех выходных данных — CSV, базы истории, панели управления и кэшей. | | `output.wiki_security_page` | `""` | Опциональный путь к странице уязвимостей в формате Markdown. Записывается только при указании. | | `output.dashboard_share` | `""` | Опциональный путь/общий ресурс, куда копируется панель управления после каждого запуска. Пустое значение = только локально. | ## Выходные данные Всё перечисленное попадает в `output.report_dir`. - **`vuln-report-YYYY-MM-DD.csv`** — отчёт по каждой находке. 28 столбцов (схема ниже). - **`vuln-history.db`** — история в SQLite (требует `sqlite3`): `runs` (сводки по запускам), `findings` (история открытых находок по `(device, software, cve)` с `first_seen` / `last_seen` / `resolved_date` / `epss_score`) и `changes` (типизированные события неделя к неделе). - **`cve-dashboard.html`** — самодостаточная HTML-панель (библиотека графиков встроена, внешних запросов нет). Опционально копируется в `output.dashboard_share`. - **`eol-report-YYYY-MM-DD.csv`** — записывается при обнаружении семейств ПО, достигших конца поддержки. - **`denied-report-YYYY-MM-DD.csv`** — записывается, если настроен `denied_software_csv` и есть совпадения. - **Файлы кэша** — `nvd-cache.json`, `kev-cache.json`, `epss-cache.json`, `eol-cache.json`, `msrc-cache.json`, `cvelist-cache.json` (все игнорируются git). - **Страница уязвимостей в Markdown** — в `output.wiki_security_page`, только если задан. ### Схема CSV (28 столбцов) Набор столбцов фиксирован и контролируется проверкой при выпуске. По порядку: | # | Колонка | Значение | | --- | --- | --- | | 1 | `device_id` | Идентификатор устройства в RMM (или детерминированный синтетический идентификатор в режиме `-InputCsv`). | | 2 | `hostname` | Имя устройства. | | 3 | `os` | Строка операционной системы (может быть пустой). | | 4 | `software` | Название продукта по данным инвентаризации. | | 5 | `installed_version` | Строка версии по данным инвентаризации. | | 6 | `cve_id` | Идентификатор CVE. | | 7 | `cvss_score` | Базовая оценка CVSS (`0`, если нет). | | 8 | `severity` | Метка уровня серьёзности CVSS. | | 9 | `cvss_version` | Какая версия CVSS использовалась (`4.0`/`3.1`/`3.0`/`2.0`/пусто). | | 10 | `epss_score` | Вероятность EPSS от FIRST.org (пусто, если CVE отсутствует в каталоге). | | 11 | `epss_percentile` | Процентиль EPSS (пусто, если отсутствует). | | 12 | `ssvc_exploitation` | Сырой параметр эксплуатации CISA SSVC (`none`/`poc`/`active`; пусто без данных ADP). | | 13 | `ssvc_decision` | Решение SSVC: `Act`/`Attend`/`Track` (пусто без данных ADP). | | 14 | `status` | `VULNERABLE` / `PATCHED` / `UNKNOWN`. | | 15 | `published_date` | Дата публикации CVE. | | 16 | `unknown_reason` | Почему строка имеет статус UNKNOWN (`SUSPECT_VERSION` / `NO_VERSION_BOUND` / `VERSION_PARSE_ERROR`; пусто в остальных случаях). | | 17 | `is_kev` | `True`, если CVE есть в каталоге CISA KEV. | | 18 | `kev_due_date` | Дата устранения по CISA KEV. | | 19 | `kev_ransomware_use` | Флаг использования в кампаниях программ-вымогателей KEV. | | 20 | `fix_version` | Вычисленная граничная версия исправления. | | 21 | `fix_boundary_inclusive` | Является ли граница исправления включительной. | | 22 | `advisory_url` | URL-адрес уведомления поставщика или подробностей NVD. | | 23 | `patch_evidence` | Свидетельство, когда Microsoft UNKNOWN был переключён на PATCHED. | | 24 | `triage_status` | Статус триажа из `triage.json` (пусто, если нет). | | 25 | `triage_note` | Заметка триажа. | | 26 | `first_seen` | Дата первого обнаружения находки (из базы истории). | | 27 | `sla_due` | Срок выполнения SLA. | | 28 | `sla_breach` | Превышен ли SLA. | ## Планирование `Run-WeeklyScan.ps1` — это обёртка для запланированных запусков: он запускает сканер из своего каталога, захватывает **все** выходные потоки в файл с датой в `<каталог скрипта>/logs/` (переопределяется через `-LogDir`) и передаёт код возврата сканера, чтобы планировщик видел сбои. **Планировщик задач Windows:** ```bat schtasks /Create /TN "CVE-Scan-Weekly" ^ /TR "pwsh -NoProfile -File C:\путь\к\Run-WeeklyScan.ps1" ^ /SC WEEKLY /D SUN /ST 22:00 /RU SYSTEM ``` **cron (Linux/macOS):** ```cron 0 22 * * 0 pwsh -File /путь/к/Run-WeeklyScan.ps1 ``` ## Размещение панели управления `cve-dashboard.html` полностью самодостаточен — открывайте его напрямую, размещайте на любом статическом хосте или файловом общем ресурсе, либо автоматически копируйте после каждого запуска через `output.dashboard_share`. Для размещения с контролем доступа в каталоге [`dashboard-auth/`](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/dashboard-auth) находится минимальный хост ASP.NET с аутентификацией Microsoft Entra (Azure AD); смотрите его [README](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/dashboard-auth/README.md) и [RUNBOOK](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/dashboard-auth/RUNBOOK.md). ## Честные ограничения (прочтите, прежде чем доверять) - **Сопоставление CPE по своей природе неточно.** 40–60% типичной инвентаризации не имеют записи в NVD и пропускаются (логируются, но не молча). Выбор CPE по имени может указать неправильный продукт; процедура подавления существует, потому что это необходимо. Это потолок бесплатного подхода NVD — платный сканер с курируемым каталогом обнаружения будет точнее. Ценность этого инструмента в том, что он бесплатный, прозрачный и проверяемый. - **UNKNOWN — это полноценный вердикт.** Когда сканер не может доказать «уязвим» или «исправлен», он заявляет об этом, а не угадывает. - **Проверено только с NinjaOne.** Движок сканирования не зависит от RMM (входные данные — просто устройство + ПО + версия); режим `-InputCsv` позволяет использовать экспорт из любого RMM/SCCM/Intune, но только адаптер NinjaOne работал в продакшене. Полный, безжалостный список — процент пропуска CPE, маскировка ключа дедупликации, `totalResults` только первой страницы, пропуски в структуре замещения MSRC и другое — в [docs/known-limitations.md](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/docs/known-limitations.md). ## Примечания по дизайну Несколько структурных решений, которые выглядят нестандартно, сделаны намеренно: - **Один файл, а не модуль.** Горячий путь сканирования выполняется внутри `ForEach-Object -Parallel`, и эти пространства выполнения не видят функции внешней области видимости — поэтому несколько вспомогательных функций намеренно продублированы внутри параллельного блока. Расположение «одна функция на файл» в модуле не устранило бы это дублирование (параллельный блок всё равно требовал бы внедрения исходного кода в каждое пространство выполнения); это просто добавило бы шаг сборки и нарушило модель развёртывания «скопировал один файл». Если проект когда-нибудь перерастёт однофайловую структуру, упаковка будет изменением в v2, а не патчем. - **Записи `[PSCustomObject]`, а не классы.** Записи находок создаются внутри параллельных пространств выполнения и передаются обратно в основное пространство. Экземпляры классов PowerShell не всегда надёжно проходят через эту границу; `[PSCustomObject]` — да, а контракт столбцов CSV обеспечивается проверкой при выпуске, а не системой типов. - **Простые тестовые скрипты на PS7, а не Pester.** Наборы в `tests/` — это скрипты без зависимостей с проверкой кода возврата, которые можно запускать везде, где есть `pwsh`, включая сервер сканирования, без установки чего-либо. CI запускает их все на Windows и Ubuntu. ## Документация - [docs/how-it-works.md](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/docs/how-it-works.md) — конвейер сканирования по этапам, логика вердиктов, дисциплина кэширования и контракт «мягкого отказа». - [docs/known-limitations.md](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/docs/known-limitations.md) — все известные способы, которыми сканер может ошибиться, и почему. - [docs/rate-limiting.md](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/docs/rate-limiting.md) — уроки ограничения скорости NVD как руководство для оператора и таблица настройки. - [docs/rmm-adapters.md](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/docs/rmm-adapters.md) — контракт входных данных, два встроенных пути инвентаризации и как добавить собственный адаптер. - [docs/HISTORY.md](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/docs/HISTORY.md) — сжатая, очищенная история версий и инженерные уроки каждого релиза. ## Участие в разработке Принимаются Pull Request’ы — особенно для адаптеров RMM. См. [CONTRIBUTING.md](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/CONTRIBUTING.md) для базовых правил и требования «безопасного направления» (логика статуса может ошибаться только в сторону **большей** видимости, никогда молча в сторону PATCHED). ## Источники данных и атрибуция Этот инструмент полезен настолько, насколько полезны бесплатные авторитетные каналы, с которыми он работает. Каждый из перечисленных запрашивается во время сканирования; пожалуйста, соблюдайте их условия, если вы строите что-то на их основе. - **NIST National Vulnerability Database (NVD)** — записи CVE и затронутые версионные диапазоны через API NVD. Данный продукт использует API NVD, но не одобрен и не сертифицирован NVD. <https://nvd.nist.gov/> - **CISA Known Exploited Vulnerabilities (KEV) Catalog** — авторитетный список CVE, предположительно эксплуатируемых в дикой природе. <https://www.cisa.gov/known-exploited-vulnerabilities-catalog> - **CVE Program — `cvelistV5`** — записи CVE плюс данные SSVC / vulnrichment от CISA ADP. CVE® — зарегистрированный товарный знак The MITRE Corporation. <https://github.com/CVEProject/cvelistV5> - **SSVC (Stakeholder-Specific Vulnerability Categorization)** — логика решения «действовать / обратить внимание / отслеживать» в этом инструменте основана на CERT/CC SSVC (Carnegie Mellon University Software Engineering Institute) и дереве решений координатора SSVC CISA, версия 2.0.3. Требуется указание CERT/CC и CISA. <https://www.cisa.gov/ssvc> · <https://github.com/CERTCC/SSVC> - **FIRST.org EPSS (Exploit Prediction Scoring System)** — вероятность эксплуатации в ближайшие 30 дней. Данные EPSS предоставлены FIRST. См. 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)** — API CVRF, используется для подтверждения того, что обновление безопасности Microsoft уже покрывает находку. © Microsoft Corporation. <https://api.msrc.microsoft.com/> · <https://msrc.microsoft.com/> - **endoflife.date** — даты окончания срока поддержки для ПО, которое больше никогда не будет пропатчено. Данные предоставлены проектом endoflife.date. <https://endoflife.date/> ## Лицензия Apache-2.0 — см. [LICENSE](https://github.com/boostedchaos/fleet-cve-scanner/blob/main/LICENSE).