//go:build windows package client import ( "encoding/json" "strings" ) const kevProbeScript = ` $ErrorActionPreference = 'SilentlyContinue' $out = [ordered]@{} # Exchange (ProxyLogon / ProxyLogon family) $exSvc = @(Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.Name -like 'MSExchange*' -or $_.DisplayName -like '*Exchange*' }) $exReg = Test-Path 'HKLM:\SOFTWARE\Microsoft\ExchangeServer' $out.exchange_installed = ($exSvc.Count -gt 0 -or $exReg) # Domain Controller (Zerologon surface) try { $dc = (Get-CimInstance Win32_ComputerSystem).DomainRole -in 4,5 } catch { $dc = $false } $out.is_domain_controller = $dc # Pulse / Ivanti VPN client or service $pulse = @(Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -match 'Pulse|Ivanti|Juniper Pulse' -or $_.Name -match 'Pulse' }) $out.pulse_present = ($pulse.Count -gt 0) # Citrix ADC / Gateway / Workspace server components $citrix = @( Test-Path 'C:\inetpub\scripts', (Test-Path 'C:\Program Files\Citrix'), (Test-Path 'C:\Program Files (x86)\Citrix') ) | Where-Object { $_ } $out.citrix_present = ($citrix.Count -gt 0) # F5 BIG-IP local management (rare on desktop) $f5 = @(Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'bigip|f5' }) $out.f5_process = ($f5.Count -gt 0) # Confluence / Atlassian stack $conf = @(Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Path -match 'atlassian|confluence|tomcat' -or $_.ProcessName -match 'confluence|tomcat' }) $out.confluence_like = ($conf.Count -gt 0) # ManageEngine ADSelfService Plus $me = @( Test-Path 'C:\Program Files\ManageEngine', Test-Path 'C:\ManageEngine' ) | Where-Object { $_ } $out.manageengine_present = ($me.Count -gt 0) # Fortinet FortiClient $forti = @(Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'forti' }) $out.forticlient = ($forti.Count -gt 0) # VMware vCenter / vSphere client heavy installs $vmw = @(Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'vpxd|VMware' }) $out.vmware_serverish = ($vmw.Count -gt 0) # Print Spooler (PrintNightmare surface) try { $sp = Get-Service Spooler $out.spooler_running = ($sp.Status -eq 'Running') } catch { $out.spooler_running = $false } # Log4j jars — shallow search (bounded) $log4j = @() $roots = @( $env:ProgramFiles, ${env:ProgramFiles(x86)}, 'C:\ProgramData' ) | Where-Object { $_ -and (Test-Path $_) } foreach ($root in $roots) { $log4j += Get-ChildItem -Path $root -Filter 'log4j-core*.jar' -Recurse -Depth 3 -ErrorAction SilentlyContinue | Select-Object -First 5 -ExpandProperty FullName } $out.log4j_jars = @($log4j | Select-Object -Unique) $out | ConvertTo-Json -Compress -Depth 4 ` type kevProbeResult struct { ExchangeInstalled bool `json:"exchange_installed"` IsDomainController bool `json:"is_domain_controller"` PulsePresent bool `json:"pulse_present"` CitrixPresent bool `json:"citrix_present"` F5Process bool `json:"f5_process"` ConfluenceLike bool `json:"confluence_like"` ManageEnginePresent bool `json:"manageengine_present"` FortiClient bool `json:"forticlient"` VMwareServerish bool `json:"vmware_serverish"` SpoolerRunning bool `json:"spooler_running"` Log4jJars []string `json:"log4j_jars"` } func runKEVProbe() (*kevProbeResult, error) { out, err := silentCombinedOutput( "powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", kevProbeScript, ) if err != nil { return nil, err } raw := strings.TrimSpace(string(out)) if idx := strings.LastIndex(raw, "{"); idx > 0 { raw = raw[idx:] } var p kevProbeResult if err := json.Unmarshal([]byte(raw), &p); err != nil { return nil, err } return &p, nil } func scanKEVExposure(patch *PatchStatusReport, ports *ListenPortsReport, sec *SysCheckSecurity) *KEVScanReport { probe, probeErr := runKEVProbe() findings := make([]KEVFinding, 0, len(KEVCatalog)) patchDays := -1 if patch != nil && patch.LastPatchDays != nil { patchDays = *patch.LastPatchDays } listening := map[int]bool{} if ports != nil { for _, p := range ports.Ports { listening[p.Port] = true } } for _, e := range KEVCatalog { f := KEVFinding{ CVE: e.ID, Name: e.Name, Product: e.Product, Severity: e.Severity, CISAKEV: e.CISAKEV, Status: "clear", Detail: e.Description, } if probeErr != nil { f.Status = "n/a" f.Detail = "probe unavailable" findings = append(findings, f) continue } switch e.ID { case "CVE-2021-26855", "CVE-2020-0688": if probe.ExchangeInstalled { f.Status = "exposed" f.Detail = "Microsoft Exchange services/registry detected — verify Mar 2021+ CU patches" if patchDays >= 0 && patchDays > 90 { f.Status = "likely" f.Detail += "; host patch age > 90 days" } } case "CVE-2020-1472": if probe.IsDomainController { f.Status = "likely" f.Detail = "Domain controller role — ensure Aug 2020 Netlogon patch (Zerologon) applied" if patchDays >= 0 && patchDays > 60 { f.Status = "exposed" f.Detail = "DC with patch age > 60 days — Zerologon mitigation urgency" } } case "CVE-2021-44228": if len(probe.Log4jJars) > 0 { f.Status = "likely" f.Detail = "log4j-core JAR(s) found: " + strings.Join(probe.Log4jJars, "; ") } case "CVE-2019-19781": if probe.CitrixPresent { f.Status = "likely" f.Detail = "Citrix install paths present — verify ADC/Gateway patch level if server role" } case "CVE-2019-11510": if probe.PulsePresent { f.Status = "likely" f.Detail = "Pulse/Ivanti VPN software detected — verify appliance firmware if VPN gateway" } case "CVE-2020-5902", "CVE-2022-1388": if probe.F5Process { f.Status = "likely" f.Detail = "F5-related process detected" } else if listening[443] { f.Status = "likely" f.Detail = "TCP/443 listener present — verify F5/BIG-IP patch level if applicable" } case "CVE-2021-26084", "CVE-2022-26134": if probe.ConfluenceLike { f.Status = "likely" f.Detail = "Atlassian/Confluence-like Java process — verify Confluence patch level" } case "CVE-2021-40539": if probe.ManageEnginePresent { f.Status = "likely" f.Detail = "ManageEngine directory present — verify ADSelfService Plus version" } case "CVE-2018-13379": if probe.FortiClient { f.Status = "likely" f.Detail = "Fortinet client process running — verify FortiOS/FortiClient versions on VPN edge" } case "CVE-2021-21972": if probe.VMwareServerish { f.Status = "likely" f.Detail = "VMware server-style services detected — verify vCenter patch level" } case "CVE-2021-34527": if probe.SpoolerRunning && !probe.IsDomainController { f.Status = "likely" f.Detail = "Print Spooler running — restrict if not required (PrintNightmare era)" } } // Stale patching amplifies any likely/exposed KEV surface if f.Status == "likely" && patchDays > 120 { f.Detail += " · OS patches older than 120 days" } findings = append(findings, f) } r := finalizeKEVReport(findings) if r.ExposedCount > 0 || r.CriticalCount > 0 { r.Summary = "CISA KEV-style exposure indicators detected — patch or isolate affected roles" } else if r.LikelyCount > 0 { r.Summary = "Some KEV-related software stacks detected — verify versions and patches" } else { r.Summary = "No high-confidence KEV exposure indicators on this host" } return r }