// SPDX-License-Identifier: GPL-3.0-or-later package detectors import ( "fmt" "strings" "codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/config" "codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model" "codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/netutil" ) var watchedSocketKinds = map[string]bool{ "raw": true, "sctp": true, "dccp": true, "packet": true, "mptcp": true, "tipc": true, "xdp": true, "vsock": true, } var alwaysHighSocketKinds = map[string]bool{"raw": true, "packet": true, "xdp": true} var activeSocketStates = map[string]bool{ "ESTAB": true, "LISTEN": true, "UNCONN": true, "CONNECTED": true, "SYN-SENT": true, "SYN-RECV": true, } // StealthNetwork ports unusual socket-family detection (SID 100036). func StealthNetwork(state model.State, cfg config.Config) []model.Alert { alerts := make([]model.Alert, 0) for _, socket := range state.Sockets { kind := strings.ToLower(socket.Kind) // TCP/UDP belong to reverse-shell, egress, and listener detectors. This // detector is deliberately limited to less-common covert-channel paths. if !watchedSocketKinds[kind] || cfg.StealthNetworkAllowKinds[kind] { continue } if socket.Comm != "" && cfg.StealthNetworkAllowComms[socket.Comm] { continue } if socket.State != "" && !activeSocketStates[socket.State] { continue } severity := "MEDIUM" host, _ := netutil.SplitHostPort(socket.Peer) publicPeer := netutil.IsPublicIP(host) && !netutil.IPInCIDRs(host, cfg.EgressAllowCIDRs) // Raw/packet/XDP access, listeners, and public peers are immediately // high severity; inactive or locally scoped unusual families stay medium. if alwaysHighSocketKinds[kind] || socket.State == "LISTEN" || publicPeer { severity = "HIGH" } comm := socket.Comm if comm == "" { comm = "?" } alerts = append(alerts, model.Alert{ SID: 100036, Severity: severity, Signature: "stealth_network", Classtype: "covert-channel", Key: socketKey(kind, socket), Detail: fmt.Sprintf( "%s socket state=%s local=%s peer=%s comm=%s pid=%s", kind, socket.State, socket.Local, socket.Peer, comm, optionalDisplayInt(socket.PID)), PIDs: alertPIDs(socket.PID), }) } return alerts }