enodia-sentinal/go-agent/internal/detectors/credential_access.go

104 lines
2.7 KiB
Go

// SPDX-License-Identifier: GPL-3.0-or-later
package detectors
import (
"fmt"
"path/filepath"
"strings"
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/config"
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model"
)
var exactCredentialFiles = map[string]bool{
"/etc/shadow": true, "/etc/gshadow": true, "/etc/security/opasswd": true,
}
var browserSecretNames = map[string]bool{
"logins.json": true, "key4.db": true, "Login Data": true, "Cookies": true,
}
var browserPathMarkers = []string{
"/.mozilla/", "/firefox/", "/chromium/", "/google-chrome/",
"/chrome/", "/brave", "/vivaldi/", "/edge/",
}
// CredentialAccess ports the Python credential_access detector (SID 100033).
func CredentialAccess(state model.State, cfg config.Config) []model.Alert {
alerts := make([]model.Alert, 0)
for _, process := range state.Processes {
comm := process.Comm
if comm == "" {
comm = "?"
}
if cfg.CredentialAccessAllowComms[comm] {
continue
}
for _, fd := range sortedFDs(process.FDTargets) {
target := strings.TrimSuffix(process.FDTargets[fd], " (deleted)")
kind := sensitiveCredentialKind(target, cfg.CredentialAccessExtraPaths)
if kind == "" {
continue
}
alerts = append(alerts, model.Alert{
SID: 100033,
Severity: "CRITICAL",
Signature: "credential_access",
Classtype: "credential-theft",
Key: fmt.Sprintf("cred:%d:%s", process.PID, target),
Detail: fmt.Sprintf(
"pid=%d comm=%s fd=%s has %s open: %s",
process.PID, comm, fd, kind, target),
PIDs: []int{process.PID},
})
break
}
}
return alerts
}
func sensitiveCredentialKind(path string, extraPaths []string) string {
if !strings.HasPrefix(path, "/") {
return ""
}
if exactCredentialFiles[path] {
return "system credential database"
}
if matchesExtraPath(path, extraPaths) {
return "configured credential path"
}
if strings.Contains(path, "/.ssh/") {
name := filepath.Base(path)
if strings.HasPrefix(name, "id_") || strings.HasSuffix(name, ".pem") || strings.HasSuffix(name, ".key") {
return "private SSH key"
}
}
if strings.HasPrefix(path, "/etc/NetworkManager/system-connections/") {
return "NetworkManager secret profile"
}
if browserSecretNames[filepath.Base(path)] {
lowered := strings.ToLower(path)
for _, marker := range browserPathMarkers {
if strings.Contains(lowered, marker) {
return "browser credential store"
}
}
}
return ""
}
func matchesExtraPath(path string, entries []string) bool {
for _, entry := range entries {
if entry == "" {
continue
}
if strings.HasSuffix(entry, "/") && strings.HasPrefix(path, entry) {
return true
}
if path == entry || strings.HasPrefix(path, strings.TrimRight(entry, "/")+"/") {
return true
}
}
return false
}