diff --git a/plugins/alerts/linux_alert_volume_test.go b/plugins/alerts/linux_alert_volume_test.go new file mode 100644 index 000000000..4de14c7c1 --- /dev/null +++ b/plugins/alerts/linux_alert_volume_test.go @@ -0,0 +1,58 @@ +package main + +// Fabricated journald records run through the Linux raw model and the pinned +// SDK CEL. They pin the de-duplication key that keeps "Audit or Logging Service +// Disabled" from opening a new alert for every matching record: its groupBy +// named origin.* fields, which an alert never carries. The EventProcessor +// playground separately runs the real parser and alert plugins. +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/threatwinds/go-sdk/plugins" + "github.com/threatwinds/go-sdk/utils" + "github.com/tidwall/gjson" + "google.golang.org/protobuf/encoding/protojson" +) + +func TestLinuxLoggingServiceAlertVolume(t *testing.T) { + blob, err := utils.ReadPbYaml("../../rules/linux/debian_family/auditd_syslog_disabling.yml") + if err != nil { + t.Fatal(err) + } + r := new(plugins.Rule) + if err = protojson.Unmarshal(blob, r); err != nil { + t.Fatal(err) + } + r.Normalize() + if want := []string{"dataSource"}; len(r.GroupBy) != 0 || !reflect.DeepEqual(r.DeduplicateBy, want) { + t.Fatalf("grouping got groupBy %v deduplicateBy %v, want deduplicateBy %v", r.GroupBy, r.DeduplicateBy, want) + } + cache := plugins.NewCELCache("linux-alert-volume") + for _, tc := range []struct { + message string + want bool + }{ + {"systemctl stop auditd", true}, + {"systemctl disable rsyslog.service", true}, + {"systemctl mask systemd-journald.service", true}, + {"systemctl restart rsyslog.service", false}, + {"Started Session 42 of User reviewer.", false}, + } { + t.Run(tc.message, func(t *testing.T) { + raw, err := json.Marshal(map[string]any{"MESSAGE": tc.message, "SYSLOG_IDENTIFIER": "sudo", "_HOSTNAME": "web01"}) + if err != nil { + t.Fatal(err) + } + event := linuxActionResultNormalize(t, string(raw)) + got, err := cache.Eval(r.Where, event) + if err != nil || got != tc.want { + t.Fatalf("where got %v (%v), want %v for %s", got, err, tc.want, event) + } + if got && gjson.Get(event, "dataSource").String() == "" { + t.Fatalf("de-duplication key dataSource does not resolve in %s", event) + } + }) + } +} diff --git a/rules/linux/debian_family/auditd_syslog_disabling.yml b/rules/linux/debian_family/auditd_syslog_disabling.yml index b1dc69f1d..c371e5552 100644 --- a/rules/linux/debian_family/auditd_syslog_disabling.yml +++ b/rules/linux/debian_family/auditd_syslog_disabling.yml @@ -1,4 +1,4 @@ -# Rule version v1.0.0 +# Rule version v1.1.0 dataTypes: - linux @@ -13,7 +13,7 @@ adversary: origin references: - https://attack.mitre.org/techniques/T1562/001/ description: | - Detects attempts to stop or disable audit and logging services (auditd, rsyslog, syslog-ng, journald) which attackers do to prevent their activities from being recorded. + Detects attempts to stop or disable audit and logging services (auditd, rsyslog, syslog-ng, journald) which attackers do to prevent their activities from being recorded. One alert is raised per host; repeats are suppressed for seven days. Next Steps: 1. Immediately investigate the host where logging was disabled @@ -30,6 +30,5 @@ where: | contains("log.message", "syslog-ng") || contains("log.message", "journald") || contains("log.message", "syslog")) && !(contains("log.message", "restart") || contains("log.message", "reload")) -groupBy: - - origin.host - - origin.user +deduplicateBy: + - dataSource