Cybersecurity-Projects/PROJECTS/beginner/systemd-persistence-scanner/internal/scanner/pam.go

108 lines
2.1 KiB
Go

/*
©AngelaMos | 2026
pam.go
Scans PAM configuration for authentication backdoor indicators
Checks /etc/pam.d/ for pam_exec.so entries that run custom scripts
and for references to non-standard PAM modules that may accept any
credential.
MITRE ATT&CK:
T1556.003 - Modify Authentication Process: Pluggable Authentication Modules
*/
package scanner
import (
"strings"
"github.com/CarterPerez-dev/sentinel/pkg/types"
)
const (
pamScannerName = "pam"
mitrePAM = "T1556.003"
)
func init() {
Register(&PAMScanner{})
}
type PAMScanner struct{}
func (p *PAMScanner) Name() string {
return pamScannerName
}
func (p *PAMScanner) Scan(
root string,
) []types.Finding {
var findings []types.Finding
pamD := ResolveRoot(root, "/etc/pam.d")
for _, path := range ListFiles(pamD) {
findings = append(
findings,
p.scanPamConfig(path)...,
)
}
return findings
}
func (p *PAMScanner) scanPamConfig(
path string,
) []types.Finding {
lines := ReadLines(path)
if lines == nil {
return nil
}
var findings []types.Finding
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if IsCommentOrEmpty(trimmed) {
continue
}
if strings.Contains(trimmed, "pam_exec.so") {
sev := types.SeverityMedium
if NetworkToolPattern.MatchString(trimmed) ||
TempDirPattern.MatchString(trimmed) {
sev = types.SeverityHigh
}
findings = append(findings, types.Finding{
Scanner: pamScannerName,
Severity: sev,
Title: "pam_exec.so runs external command",
Path: path,
Evidence: trimmed,
MITRE: mitrePAM,
})
}
if strings.Contains(trimmed, "pam_permit.so") &&
containsAuthContext(trimmed) {
findings = append(findings, types.Finding{
Scanner: pamScannerName,
Severity: types.SeverityHigh,
Title: "pam_permit.so in auth context (accepts any credential)",
Path: path,
Evidence: trimmed,
MITRE: mitrePAM,
})
}
}
return findings
}
func containsAuthContext(line string) bool {
fields := strings.Fields(line)
if len(fields) < 1 {
return false
}
return fields[0] == "auth"
}