208 lines
4.7 KiB
Go
208 lines
4.7 KiB
Go
package deploy
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// commonLANPorts are probed on ARP/subnet LAN targets (enumeration only).
|
|
var commonLANPorts = []int{22, 445, 3389, 5985, 5986, 2375, 8080, 8443, 2222}
|
|
|
|
// portServiceNames maps well-known ports to friendly service labels.
|
|
var portServiceNames = map[int]string{
|
|
22: "ssh",
|
|
445: "smb",
|
|
3389: "rdp",
|
|
5985: "winrm",
|
|
5986: "winrm-https",
|
|
2375: "docker-api",
|
|
8080: "http-alt",
|
|
8443: "https-alt",
|
|
2222: "ssh-alt",
|
|
}
|
|
|
|
// RunServiceDiscover performs local + LAN service enumeration and returns JSON.
|
|
func RunServiceDiscover(maxLANHosts int) string {
|
|
if maxLANHosts <= 0 {
|
|
maxLANHosts = 32
|
|
}
|
|
if maxLANHosts > MaxSubnetScanHosts {
|
|
maxLANHosts = MaxSubnetScanHosts
|
|
}
|
|
|
|
localIP := localIPv4ForDiscovery()
|
|
localSubnet := getSubnet(localIP)
|
|
|
|
passive := collectPassiveHints()
|
|
hints := CollectNetworkHints(maxLANHosts)
|
|
for _, h := range appendNetworkHintStrings(hints) {
|
|
passive = append(passive, h)
|
|
}
|
|
|
|
result := ServiceDiscoverResult{
|
|
ProbedAt: time.Now().UTC().Format(time.RFC3339),
|
|
PassiveHints: passive,
|
|
Local: ServiceGraphHost{
|
|
Host: localIP,
|
|
Subnet: localSubnet,
|
|
Services: probeLocalServices(),
|
|
},
|
|
}
|
|
|
|
lanHosts := discoverLANServiceGraph(maxLANHosts)
|
|
result.LANHosts = lanHosts
|
|
|
|
b, _ := json.Marshal(result)
|
|
return string(b)
|
|
}
|
|
|
|
func localIPv4ForDiscovery() string {
|
|
ips := getLocalIPs()
|
|
for _, ip := range ips {
|
|
if isIPv4(ip) {
|
|
return ip
|
|
}
|
|
}
|
|
if ip, err := PrimaryLocalIPv4(); err == nil && ip != "" {
|
|
return ip
|
|
}
|
|
return "127.0.0.1"
|
|
}
|
|
|
|
func discoverLANServiceGraph(maxHosts int) []ServiceGraphHost {
|
|
targets := lanDiscoveryTargets(maxHosts)
|
|
localSet := make(map[string]bool)
|
|
for _, ip := range getLocalIPs() {
|
|
localSet[ip] = true
|
|
}
|
|
|
|
var hosts []ServiceGraphHost
|
|
for _, host := range targets {
|
|
if localSet[host] {
|
|
continue
|
|
}
|
|
entries := probeLANHostServices(host)
|
|
if len(entries) == 0 {
|
|
continue
|
|
}
|
|
hosts = append(hosts, ServiceGraphHost{
|
|
Host: host,
|
|
Subnet: getSubnet(host),
|
|
Services: entries,
|
|
})
|
|
}
|
|
return hosts
|
|
}
|
|
|
|
func appendNetworkHintStrings(h NetworkHints) []string {
|
|
var out []string
|
|
if h.DomainJoined {
|
|
out = append(out, "domain_joined:"+h.DomainName)
|
|
}
|
|
if h.PreferJoinLane != "" {
|
|
out = append(out, "prefer_join_lane:"+h.PreferJoinLane)
|
|
}
|
|
for _, s := range h.LdapSRV {
|
|
out = append(out, "ldap_srv:"+s)
|
|
}
|
|
for _, s := range h.KerberosSRV {
|
|
out = append(out, "kerberos_srv:"+s)
|
|
}
|
|
if h.EnterpriseCodeSignCert {
|
|
out = append(out, "enterprise_code_sign")
|
|
}
|
|
return out
|
|
}
|
|
|
|
// lanDiscoveryTargets merges ARP cache neighbors with a capped /24 port knock (Path Tracer LAN discovery).
|
|
func lanDiscoveryTargets(maxHosts int) []string {
|
|
seen := make(map[string]bool)
|
|
var out []string
|
|
|
|
add := func(ip string) {
|
|
ip = strings.TrimSpace(ip)
|
|
if ip == "" || !isIPv4(ip) || seen[ip] {
|
|
return
|
|
}
|
|
seen[ip] = true
|
|
out = append(out, ip)
|
|
}
|
|
|
|
for _, ip := range arpHosts() {
|
|
if len(out) >= maxHosts {
|
|
return out
|
|
}
|
|
add(ip)
|
|
}
|
|
|
|
for _, ip := range getLocalIPs() {
|
|
if !isIPv4(ip) || len(out) >= maxHosts {
|
|
continue
|
|
}
|
|
subnet := getSubnet(ip)
|
|
if subnet == "" {
|
|
continue
|
|
}
|
|
for i := 1; i < 255 && len(out) < maxHosts; i++ {
|
|
candidate, ok := ipv4SweepHost(subnet, i)
|
|
if !ok {
|
|
break
|
|
}
|
|
if candidate == ip {
|
|
continue
|
|
}
|
|
if open := probePorts(candidate, commonLANPorts); len(open) > 0 {
|
|
add(candidate)
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func probeLANHostServices(host string) []ServiceGraphEntry {
|
|
var entries []ServiceGraphEntry
|
|
open := probePorts(host, commonLANPorts)
|
|
for _, p := range open {
|
|
name := portServiceNames[p]
|
|
if name == "" {
|
|
name = "tcp/" + strconv.Itoa(p)
|
|
}
|
|
entries = append(entries, entryWithLane(name, p, "lan_port"))
|
|
}
|
|
|
|
if smbEntries := probeSMBGraphEntries(host); len(smbEntries) > 0 {
|
|
entries = append(entries, smbEntries...)
|
|
}
|
|
return dedupeEntries(entries)
|
|
}
|
|
|
|
func probeSMBGraphEntries(host string) []ServiceGraphEntry {
|
|
conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, "445"), 800*time.Millisecond)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
conn.Close()
|
|
|
|
// Windows net view enumeration is platform-specific; on Unix we only record SMB port.
|
|
if runtime.GOOS != "windows" {
|
|
return []ServiceGraphEntry{entryWithLane("smb", 445, "lan_port")}
|
|
}
|
|
out, err := HiddenOutput("net", "view", "\\\\"+host)
|
|
if err != nil {
|
|
return []ServiceGraphEntry{entryWithLane("smb", 445, "lan_port")}
|
|
}
|
|
shares := parseNetViewShares(strings.TrimSpace(string(out)))
|
|
if len(shares) == 0 {
|
|
return []ServiceGraphEntry{entryWithLane("smb", 445, "lan_port")}
|
|
}
|
|
entries := []ServiceGraphEntry{entryWithLane("smb", 445, "lan_port")}
|
|
for _, share := range shares {
|
|
entries = append(entries, entryWithLane("smb-share:"+share, 445, "smb_share"))
|
|
}
|
|
return entries
|
|
}
|