104 lines
2.7 KiB
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
|
|
}
|