
RMM으로 관리되는 환경을 위한 오픈소스 단일 스크립트 CVE 스캐너입니다. 순수 PowerShell 7 기반 — RMM 소프트웨어 인벤토리를 NVD, CISA KEV, EPSS, SSVC와 결합하여 이 버전이 취약한지, 그리고 얼마나 긴급한지에 답합니다.
RMM으로 관리되는 플릿을 위한 오픈소스 단일 스크립트 CVE 스캐너입니다. 순수 PowerShell 7, 에이전트 불필요, 어플라이언스 불필요, 라이선스 비용 없음.
관리하는 모든 엔드포인트의 모든 소프트웨어에 대해 한 가지 질문에 답합니다: 설치된 버전이 알려진 취약점을 가지고 있는지, 그리고 그 긴급성은 어느 정도인지? — RMM의 소프트웨어 인벤토리를 무료 공식 보안 피드와 결합하여.
출력물: 장치별 CSV 보고서, SLA 시계 및 주간 변경 이벤트가 있는 SQLite 히스토리, 독립형 HTML 대시보드, 그리고 선택적 마크다운 노출 보고서.
v1.0.0. 엔진은 Windows 플릿을 대상으로 프로덕션에서 주간 실행되던 스캐너에서 클린룸 방식으로 포팅되었습니다. (docs/HISTORY.md 참조). 이 릴리스 이전에 두 스캐너를 동일한 실제 플릿에 대해 실행하고 행별로 비교했습니다. 수만 건의 결과 중 두 스캐너가 동일한 *(장치, 소프트웨어, 버전)*을 확인한 경우, 모든 계산된 필드(상태, CVSS, 심각도, KEV 플래그, SSVC 결정, 수정 버전)가 정확히 일치했습니다.
이 비교는 판결(verdict) 로직을 다룹니다. 히스토리/SLA 계층을 테스트하지 않았으며, 자체 환경에서 도구를 검증하는 것을 대체하지 않습니다. 신뢰하기 전에 docs/known-limitations.md를 읽으십시오 — 이 문서는 아첨보다는 정직하게 작성되었습니다.
PowerShell 7 (pwsh)이 필요합니다. 두 가지 실행 방법:
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
인벤토리를 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를 참조하십시오.
brew install powershell; Windows: winget install Microsoft.PowerShell; Debian/Ubuntu: Microsoft 패키지 저장소(packages.microsoft.com)에서 설치.brew install sqlite; Debian/Ubuntu: apt-get install sqlite3; Windows: sqlite.org에서 SQLite "tools" 번들을 다운로드하고 sqlite3.exe를 PATH에 또는 스크립트 옆에 배치. 없어도 스캔은 실행됩니다. first_seen은 실행 날짜로 대체됩니다.first_seen 추적을 활성화합니다. 없으면 해당 기능이 건너뛰고 스캔은 계속 완료됩니다.epss.empiricalsecurity.com (EPSS 벌크 점수 호스트 — FIRST가 EPSS 데이터 호스팅을 이전), MSRC, endoflife.date, GitHub raw (CVE Program cvelistV5) — 라이브 모드에서는 NinjaOne API도 필요.구성은 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 참조.
| 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에, 설정된 경우에만.열 세트는 고정되어 있으며 게이트에서 강제됩니다. 순서:
| # | Column | Meaning |
|---|---|---|
| 1 | device_id | RMM 장치 ID(또는 -InputCsv 모드의 결정론적 합성 ID). |
| 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 | FIRST.org EPSS 확률(카탈로그에 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 |
Run-WeeklyScan.ps1은 예약된 실행을 위한 래퍼입니다: 자체 디렉터리에서 스캐너를 실행하고, 모든 출력 스트림을 <script dir>/logs/ 아래의 날짜별 로그로 캡처하며(-LogDir로 재정의 가능), 스캐너의 종료 코드를 전파하여 스케줄러가 실패를 감지할 수 있습니다.
Windows 작업 스케줄러:
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은 완전히 독립적입니다 — 직접 열거나, 정적 호스트나 파일 공유에 넣거나, output.dashboard_share를 통해 각 실행 시 자동으로 복사할 수 있습니다. 접근 제어 호스팅을 위해 dashboard-auth/ 디렉터리는 Microsoft Entra(Azure AD) 인증이 포함된 최소 ASP.NET 호스트를 제공합니다. 해당 README 및 RUNBOOK을 참조하십시오.
-InputCsv 모드를 사용하면 모든 RMM/SCCM/Intune 내보내기가 이를 공급할 수 있지만, 프로덕션에서는 NinjaOne 어댑터만 실행되었습니다.전체 포괄 목록(CPE 건너뛰기 비율, 중복 제거 키 마스킹, 1페이지 totalResults, MSRC 대체 간격 등)은 docs/known-limitations.md에 있습니다.
ForEach-Object -Parallel 내에서 실행되며, 해당 런스페이스는 외부 범위의 함수를 볼 수 없습니다. 이것이 몇 가지 도우미가 의도적으로 병렬 블록 내에서 중복되는 이유입니다. 함수당 하나의 파일 모듈 레이아웃은 해당 중복을 제거하지 않습니다(병렬 본문에는 여전히 런스페이스당 소스 주입이 필요함). 빌드 단계를 추가하고 파일 하나 복사 배포 모델을 깨뜨릴 뿐입니다. 프로젝트가 단일 파일을 벗어나게 되면 패키징은 v2 형태의 변경이지 패치가 아닙니다.[PSCustomObject] 레코드, 클래스가 아님. 발견 레코드는 병렬 런스페이스 내에서 생성되어 주 런스페이스로 마샬링됩니다. PowerShell 클래스 인스턴스는 해당 경계를 안정적으로 통과하지 못합니다. [PSCustomObject]는 통과하며, CSV 열 계약은 유형 시스템 대신 릴리스 게이트에 의해 강제됩니다.tests/의 스위트는 종속성이 없는 스크립트로, 종료 코드 어설션을 사용하며 pwsh가 존재하는 모든 곳(스캔 서버 포함)에서 실행 가능하며 설치할 것이 없습니다. CI는 Windows와 Ubuntu에서 모두 실행합니다.PR 환영합니다 — 특히 RMM 어댑터. 기본 규칙 및 안전 방향 요구 사항은 CONTRIBUTING.md를 참조하십시오(상태 로직은 더 많은 가시성 방향으로만 오류를 허용하며, 조용히 PATCHED로 오류를 허용하지 않음).
이 도구는 결합하는 무료 공식 피드만큼만 유용합니다. 다음 각각은 스캔 시 조회됩니다. 이를 기반으로 구축하는 경우 해당 조건을 준수하시기 바랍니다.
cvelistV5 — CVE 레코드와 CISA ADP가 제공한 SSVC/vulnrichment 데이터. CVE®는 MITRE Corporation의 등록 상표입니다. https://github.com/CVEProject/cvelistV5Apache-2.0 — LICENSE 참조.
CVE가 CISA KEV 카탈로그에 있으면 True. |
| 18 | kev_due_date | CISA KEV 수정 기한 날짜. |
| 19 | kev_ransomware_use | KEV 랜섬웨어 캠페인 사용 플래그. |
| 20 | fix_version | 계산된 수정 경계 버전. |
| 21 | fix_boundary_inclusive | 수정 경계 포함 여부. |
| 22 | advisory_url | 공급업체 권고 또는 NVD 상세 URL. |
| 23 | patch_evidence | Microsoft UNKNOWN이 PATCHED로 전환된 증거. |
| 24 | triage_status | triage.json의 분류 상태(없으면 빈칸). |
| 25 | triage_note | 분류 메모. |
| 26 | first_seen | 발견 항목이 처음 관찰된 날짜(히스토리 DB에서). |
| 27 | sla_due | SLA 마감 날짜. |
| 28 | sla_breach | SLA 위반 여부. |