133 lines
2.3 KiB
Go
133 lines
2.3 KiB
Go
/*
|
|
©AngelaMos | 2026
|
|
logrotate.go
|
|
|
|
Scans logrotate configuration for persistence via rotate hooks
|
|
|
|
Parses /etc/logrotate.d/ configs for postrotate, prerotate,
|
|
firstaction, and lastaction blocks that execute shell commands
|
|
as root during log rotation. Attackers embed callbacks here
|
|
because these files are rarely audited.
|
|
|
|
MITRE ATT&CK:
|
|
T1053.003 - Scheduled Task/Job: Cron
|
|
*/
|
|
|
|
package scanner
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/CarterPerez-dev/sentinel/pkg/types"
|
|
)
|
|
|
|
const (
|
|
logrotateScannerName = "logrotate"
|
|
mitreLogrotate = "T1053.003"
|
|
)
|
|
|
|
var rotateHooks = []string{
|
|
"postrotate",
|
|
"prerotate",
|
|
"firstaction",
|
|
"lastaction",
|
|
}
|
|
|
|
func init() {
|
|
Register(&LogrotateScanner{})
|
|
}
|
|
|
|
type LogrotateScanner struct{}
|
|
|
|
func (l *LogrotateScanner) Name() string {
|
|
return logrotateScannerName
|
|
}
|
|
|
|
func (l *LogrotateScanner) Scan(
|
|
root string,
|
|
) []types.Finding {
|
|
var findings []types.Finding
|
|
|
|
confD := ResolveRoot(root, "/etc/logrotate.d")
|
|
for _, path := range ListFiles(confD) {
|
|
findings = append(
|
|
findings,
|
|
l.scanConfig(path)...,
|
|
)
|
|
}
|
|
|
|
mainConf := ResolveRoot(root, "/etc/logrotate.conf")
|
|
findings = append(
|
|
findings,
|
|
l.scanConfig(mainConf)...,
|
|
)
|
|
|
|
return findings
|
|
}
|
|
|
|
func (l *LogrotateScanner) scanConfig(
|
|
path string,
|
|
) []types.Finding {
|
|
lines := ReadLines(path)
|
|
if lines == nil {
|
|
return nil
|
|
}
|
|
|
|
var findings []types.Finding
|
|
inBlock := false
|
|
blockName := ""
|
|
|
|
for _, line := range lines {
|
|
trimmed := strings.TrimSpace(line)
|
|
|
|
if trimmed == "endscript" {
|
|
inBlock = false
|
|
continue
|
|
}
|
|
|
|
for _, hook := range rotateHooks {
|
|
if trimmed == hook {
|
|
inBlock = true
|
|
blockName = hook
|
|
break
|
|
}
|
|
}
|
|
|
|
if !inBlock {
|
|
continue
|
|
}
|
|
|
|
if IsCommentOrEmpty(trimmed) {
|
|
continue
|
|
}
|
|
|
|
matched, sev, label := MatchLine(trimmed)
|
|
if matched {
|
|
findings = append(findings, types.Finding{
|
|
Scanner: logrotateScannerName,
|
|
Severity: sev,
|
|
Title: "Suspicious logrotate " +
|
|
blockName + ": " + label,
|
|
Path: path,
|
|
Evidence: trimmed,
|
|
MITRE: mitreLogrotate,
|
|
})
|
|
continue
|
|
}
|
|
|
|
if containsShellCommand(trimmed) {
|
|
findings = append(findings, types.Finding{
|
|
Scanner: logrotateScannerName,
|
|
Severity: types.SeverityMedium,
|
|
Title: "Logrotate " + blockName +
|
|
" runs shell command",
|
|
Path: path,
|
|
Evidence: trimmed,
|
|
MITRE: mitreLogrotate,
|
|
})
|
|
}
|
|
}
|
|
|
|
return findings
|
|
}
|