enodia-sentinal/go-agent/internal/detectors/stealth_network.go
2026-07-10 17:29:07 -07:00

67 lines
2.2 KiB
Go

// 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
}