Files
AetherForge/agent/client/cve_scan_windows.go
AetherForge 5fc601b564 feat: fleet ops, KEV scan, tunnels, beacon fallback, persistence
Extend owned-fleet control with scheduled tasks, audit log, file browser,
HTTPS beacon when WS drops, protocol tunnels, registry/autostart forge
options, KEV exposure in full sys check with Telegram alerts, and UI/tests.
2026-06-04 09:34:33 -07:00

232 lines
7.3 KiB
Go

//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 || listening[443] {
if probe.F5Process {
f.Status = "likely"
f.Detail = "F5-related process detected"
}
}
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
}