
fleet-cve-scanner v1.1.0
RMM으로 관리되는 환경을 위한 오픈소스 단일 스크립트 CVE 스캐너입니다. 순수 PowerShell 7 기반 — RMM 소프트웨어 인벤토리를 NVD, CISA KEV, EPSS, SSVC와 결합하여 이 버전이 취약한지, 그리고 얼마나 긴급한지에 답합니다.
fleet-cve-scanner
RMM으로 관리되는 플릿을 위한 오픈소스 단일 스크립트 CVE 스캐너입니다. 순수 PowerShell 7, 에이전트 불필요, 어플라이언스 불필요, 라이선스 비용 없음.
관리하는 모든 엔드포인트의 모든 소프트웨어에 대해 한 가지 질문에 답합니다: 설치된 버전이 알려진 취약점을 가지고 있는지, 그리고 그 긴급성은 어느 정도인지? — RMM의 소프트웨어 인벤토리를 무료 공식 보안 피드와 결합하여.
- NIST NVD — 알려진 CVE 및 영향을 받는 버전 범위
- CISA KEV — 실제 환경에서 활발히 악용됨 (점수 임계값 우회)
- EPSS — 향후 30일 내 악용 확률
- CISA SSVC — 행동/관찰/추적 우선순위 결정
- MSRC — Microsoft 패치가 이미 해당 발견을 포함하고 있다는 증거
- endoflife.date — 더 이상 패치되지 않을 소프트웨어
출력물: 장치별 CSV 보고서, SLA 시계 및 주간 변경 이벤트가 있는 SQLite 히스토리, 독립형 HTML 대시보드, 그리고 선택적 마크다운 노출 보고서.
상태
v1.0.0. 엔진은 Windows 플릿을 대상으로 프로덕션에서 주간 실행되던 스캐너에서 클린룸 방식으로 포팅되었습니다. (docs/HISTORY.md 참조). 이 릴리스 이전에 두 스캐너를 동일한 실제 플릿에 대해 실행하고 행별로 비교했습니다. 수만 건의 결과 중 두 스캐너가 동일한 *(장치, 소프트웨어, 버전)*을 확인한 경우, 모든 계산된 필드(상태, CVSS, 심각도, KEV 플래그, SSVC 결정, 수정 버전)가 정확히 일치했습니다.
이 비교는 판결(verdict) 로직을 다룹니다. 히스토리/SLA 계층을 테스트하지 않았으며, 자체 환경에서 도구를 검증하는 것을 대체하지 않습니다. 신뢰하기 전에 docs/known-limitations.md를 읽으십시오 — 이 문서는 아첨보다는 정직하게 작성되었습니다.
빠른 시작
PowerShell 7 (pwsh)이 필요합니다. 두 가지 실행 방법:
A. 라이브 NinjaOne 스캔
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
B. 오프라인 — 모든 RMM, SCCM, Intune (API 접근 불필요)
인벤토리를 hostname, software, version 열(선택 사항 device_id, os)을 가진 CSV로 내보낸 후:
cp config.example.json config.json # only output.report_dir is needed here
pwsh -File fleet-cve-scan.ps1 -InputCsv inventory.csv
-InputCsv는 전혀 NinjaOne 호출을 하지 않습니다 — OAuth 없음, API 없음. 여전히 NVD 및 기타 공개 피드를 쿼리하므로 config.json에 nvd.api_key가 필요하고 유효한 output.report_dir이 필요합니다. 입력 계약 및 RMM별 내보내기 방법은 docs/rmm-adapters.md를 참조하십시오.
필수 구성 요소 설치
- 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: sqlite.org에서 SQLite "tools" 번들을 다운로드하고sqlite3.exe를PATH에 또는 스크립트 옆에 배치. 없어도 스캔은 실행됩니다.first_seen은 실행 날짜로 대체됩니다.
요구 사항
- PowerShell 7 (7.4+ 권장). 병렬 스캔에는 7.0+가 필요합니다.
- sqlite3 — 선택 사항; 히스토리 DB, 트렌드, SLA
first_seen추적을 활성화합니다. 없으면 해당 기능이 건너뛰고 스캔은 계속 완료됩니다. - 네트워크 아웃바운드 — NVD, CISA(KEV),
epss.empiricalsecurity.com(EPSS 벌크 점수 호스트 — FIRST가 EPSS 데이터 호스팅을 이전), MSRC, endoflife.date, GitHub raw (CVE ProgramcvelistV5) — 라이브 모드에서는 NinjaOne API도 필요. - 자격 증명 — 라이브 모드는 NinjaOne OAuth2 클라이언트 ID/비밀 필요; 오프라인 모드는 인벤토리 CSV만 필요. NVD API 키는 기술적으로 선택 사항이지만 플릿 규모에서는 사실상 필수: NVD는 키 사용 시 30초에 50개 요청, 키 없이 5개 요청을 허용합니다. 스캐너는 키 유무를 감지하여 속도를 조절하므로, 키 없이 실행해도 올바르지만 약 10배 느려집니다 — 소수의 제품에는 괜찮지만 실제 플릿에는 비현실적입니다. 키는 무료입니다.
구성 참조
구성은 JSON 파일입니다(기본값은 스크립트 옆의 config.json; -ConfigPath로 재정의 가능). config.example.json에서 시작하십시오. 스캐너가 읽는 모든 키, 생략 시 기본값 및 역할:
ninjaone (라이브 모드)
| Key | Default | Purpose |
|---|---|---|
ninjaone.client_id | "" | NinjaOne API OAuth2 클라이언트 ID. 라이브 스캔에 필요; -InputCsv의 경우 비워 둠. |
ninjaone.client_secret | "" | NinjaOne API OAuth2 클라이언트 시크릿. |
ninjaone.base_url | — (example ships https://app.ninjarmm.com) | NinjaOne API 기본 URL; 라이브 스캔에 필요, 내장 대체 없음. 지역 호스트 사용(예: eu.ninjarmm.com, oc.ninjarmm.com). |
nvd
| Key | Default | Purpose |
|---|---|---|
nvd.api_key | "" | NVD API 키, apiKey 헤더로 전송. 비워두면 인증되지 않은 상태로 실행되어 nvd_rate_limit 및 nvd_min_spacing_ms의 기본값이 인증된 값(48/30s, 700ms) 대신 NVD의 익명 상한(4/30s, 6500ms)으로 낮아집니다. 무료이며 약 10배 빠릅니다. |
스캔 및 점수
| Key | Default | Purpose |
|---|---|---|
cvss_threshold | 7.0 | 점수가 있는 CVE가 VULNERABLE로 보고되기 위한 최소 CVSS 기본 점수. KEV 목록에 있는 CVE는 이 하한을 우회합니다. |
ssvc_mission_prevalence | high | SSVC Mission Prevalence 이해관계자 입력(low / medium / high). |
ssvc_public_wellbeing | high | SSVC Public Well-being 이해관계자 입력(low / medium / high). |
캐싱 및 속도 제한
| Key | Default | Purpose |
|---|---|---|
nvd_cache_ttl_days | 7 | NVD 결과(소프트웨어 이름별)가 재조회 전에 캐시에 유지되는 일수. |
nvd_cache_flush_every | 500 | N개 항목 완료 시마다 NVD 캐시를 디스크에 체크포인트하여 중단된 스캔도 가져온 내용을 유지합니다. 0은 비활성화(스캔 종료 저장은 여전히 실행). |
nvd_min_spacing_ms | 700 with an API key, 6500 without | NVD 호출 간 최소 밀리초. 바인딩 속도 제약 — 이것부터 조정하십시오. |
nvd_rate_limit | 48 with an API key, 4 without | 30초당 NVD 호출의 슬라이딩 윈도우 상한(백스톱). 최소 1. |
parallel_throttle | 20 | ForEach-Object -Parallel 런스페이스 수. NVD 호출은 관계없이 직렬화됨; 캐시 히트 동시성만 제한. 최소 1. |
kev_cache_ttl_hours | 24 | 캐시된 CISA KEV 카탈로그의 TTL. |
epss_cache_ttl_hours | 24 | 캐시된 FIRST.org EPSS 점수 파일의 TTL. |
eol_cache_ttl_days | 7 | 캐시된 endoflife.date 조회의 TTL. |
msrc_cache_ttl_days | 30 | 캐시된 MSRC CVRF 데이터의 TTL. |
cvelist_cache_ttl_days | 7 | 캐시된 CVE Program (cvelistV5) / CISA-ADP SSVC 데이터의 TTL. |
cvelist_fetch_budget | 300 | SSVC/vulnrichment 강화를 위한 실행당 최대 CVE 레코드 가져오기; 나머지는 다음 실행으로 연기. |
nvd_min_spacing_ms, nvd_rate_limit, parallel_throttle은 스캐너가 읽지만 config.example.json에는 없습니다 — 조정이 필요한 경우에만 추가하십시오. docs/rate-limiting.md 참조.
강화 및 SLA
| Key | Default | Purpose |
|---|---|---|
epss_spike_threshold | 0.10 | 스캔 간 발견 항목의 EPSS 점수 상승 최소치, EPSS_SPIKE 변경 이벤트를 발생시킴. |
sla_days_critical | 14 | CRITICAL 발견 항목에 대한 SLA 기간(first_seen 기준 일수). |
sla_days_high | 30 | 기타 점수 심각도에 대한 SLA 기간. |
denied_software_csv | "" | 선택적 거부 소프트웨어 CSV 교차 검사 경로. 비어 있으면 검사 비활성화. |
output
| Key | Default | Purpose |
|---|---|---|
output.report_dir | (required) | 모든 출력물(CSV, 히스토리 DB, 대시보드, 캐시)을 위한 디렉터리. |
output.wiki_security_page | "" | 선택적 마크다운 노출 페이지 경로. 설정된 경우에만 작성. |
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(모두 gitignore됨). - 마크다운 노출 페이지 —
output.wiki_security_page에, 설정된 경우에만.
CSV 스키마 (28개 열)
열 세트는 고정되어 있으며 게이트에서 강제됩니다. 순서: