From 41669e2929e78127d9e385e348e8841d81444d97 Mon Sep 17 00:00:00 2001 From: Ron Heichman Date: Fri, 28 Aug 2026 12:54:51 -0500 Subject: [PATCH 1/9] feat(rule): add canonical_path CEL function Expose canonical_path(string) to rules: path.Clean plus a /proc//root strip, so a rule compares one normalized target instead of enumerating traversal and proc-root disguises in bounded regex. Closes the fixed-depth traversal hole a hand-rolled regex cannot: dir/../ chains resolve at any depth. --- docs/rules.md | 7 +++ internal/model/event.go | 26 ++++++++ internal/rule/canonical_path_test.go | 92 ++++++++++++++++++++++++++++ internal/rule/engine.go | 12 ++++ 4 files changed, 137 insertions(+) create mode 100644 internal/rule/canonical_path_test.go diff --git a/docs/rules.md b/docs/rules.md index a3edd18..08f09b8 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -143,11 +143,18 @@ Common CEL operations include: | List predicates | `exists`, `all`, `exists_one` | | List range | `items.slice(start, end)` | | Integer indexes | `lists.range(n).exists(i, ...)` | +| Path normalization | `canonical_path(event.file_path)` | | Missing nullable value | `event.exit_code == null` | `matches` uses RE2 regular expressions. CEL string literals require their own escaping; for example, a literal dot is written as `"\\.env"`. +`canonical_path(p)` returns the real target a path names: it applies +`path.Clean` at any depth and strips a leading `/proc//root` +prefix. Use it to match a protected path by its resolved location instead of +enumerating `.`, `..`, `//`, and `/proc` disguises. It resolves lexical +traversal only and does not dereference symlinks. + Action types are alternatives, not layers. A recognized shell action is a `command.exec`, not both a `tool.call` and a `command.exec`; file and network actions are specialized the same way. `tool.call` is the fallback when numbat diff --git a/internal/model/event.go b/internal/model/event.go index 20bee93..48fccb5 100644 --- a/internal/model/event.go +++ b/internal/model/event.go @@ -7,6 +7,7 @@ import ( "crypto/sha256" "encoding/hex" "path" + "regexp" "sort" "strings" ) @@ -526,6 +527,31 @@ func filepathClean(p string) string { return path.Clean(NormalizeEventPath(p)) } +// procRootPrefix matches a leading /proc//root on an +// already path.Clean-ed path. A process reaches another mount namespace's file +// through this magic symlink, so /proc/self/root/etc/x names the same object as +// /etc/x. The remainder is captured so the prefix can be re-rooted to "/". +var procRootPrefix = regexp.MustCompile(`^/proc/(?:self|thread-self|[0-9]+)/root(/.*)?$`) + +// CanonicalizePath returns the real filesystem target a path names. It cleans +// the path (collapsing ., .., and duplicate separators at any depth) and then +// strips a /proc//root prefix, re-rooting it to "/". An +// empty input stays empty. It resolves lexical traversal only; it does not +// dereference symlinks, which are not visible in a static command or event. +func CanonicalizePath(p string) string { + clean := filepathClean(p) + if clean == "" { + return "" + } + if m := procRootPrefix.FindStringSubmatch(clean); m != nil { + if m[1] == "" { + return "/" + } + return m[1] + } + return clean +} + // MergeTags returns the sorted, de-duplicated union of two tag sets. func MergeTags(a, b []string) []string { set := make(map[string]struct{}, len(a)+len(b)) diff --git a/internal/rule/canonical_path_test.go b/internal/rule/canonical_path_test.go new file mode 100644 index 0000000..0b5eba0 --- /dev/null +++ b/internal/rule/canonical_path_test.go @@ -0,0 +1,92 @@ +package rule + +import ( + "testing" + + "github.com/perplexityai/numbat/internal/model" +) + +func TestCanonicalPathCollapsesTraversalForFileEvents(t *testing.T) { + eng := mustEngine(t, Rule{ + ID: "t.protect_rule_file", + Severity: model.SeverityHigh, + Expr: `event.event_type in ["file.write", "file.delete"] && canonical_path(event.file_path) == "/etc/numbat/rules/protect_numbat.yaml"`, + }) + cases := []struct { + name string + path string + want bool + }{ + {"plain", "/etc/numbat/rules/protect_numbat.yaml", true}, + {"dot_segment", "/etc/numbat/./rules/protect_numbat.yaml", true}, + {"shallow_traversal", "/etc/numbat/rules/x/../protect_numbat.yaml", true}, + {"deep_traversal_depth4", "/etc/numbat/rules/d0/d1/d2/d3/../../../../protect_numbat.yaml", true}, + {"proc_root_prefix", "/proc/self/root/etc/numbat/rules/protect_numbat.yaml", true}, + {"proc_root_pid_traversal", "/proc/4321/root/etc/numbat/rules/d0/d1/../../protect_numbat.yaml", true}, + {"duplicate_slash", "/etc/numbat//rules/protect_numbat.yaml", true}, + {"unprotected_sibling", "/etc/numbat/rules.example/protect_numbat.yaml", false}, + {"escapes_out", "/etc/numbat/rules/../ordinary.txt", false}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + ev := model.Event{EventID: "e", EventType: model.EventFileWrite, FilePath: tc.path} + matches, err := eng.Eval(ev) + if err != nil { + t.Fatalf("Eval: %v", err) + } + got := len(matches) == 1 + if got != tc.want { + t.Fatalf("path %q: matched=%v want=%v", tc.path, got, tc.want) + } + }) + } +} + +func TestCanonicalPathCollapsesTraversalForShellArgv(t *testing.T) { + eng := mustEngine(t, Rule{ + ID: "t.protect_rule_rm", + Severity: model.SeverityHigh, + Enforce: boolPtr(true), + Expr: `event.event_type == "command.exec" && shell_commands.exists(command, command.name in ["rm", "unlink"] && command.argv.exists(a, canonical_path(a) == "/usr/local/bin/numbat"))`, + }) + cases := []struct { + name string + command string + want bool + }{ + {"plain", "rm -f /usr/local/bin/numbat", true}, + {"deep_traversal_depth5", "rm -f /usr/local/bin/d0/d1/d2/d3/d4/../../../../../numbat", true}, + {"proc_root", "rm -f /proc/self/root/usr/local/bin/numbat", true}, + {"benign_other", "rm -f /tmp/numbat", false}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + ev := model.Event{EventID: "e", EventType: model.EventCommandExec, Command: tc.command} + matches, err := eng.Eval(ev) + if err != nil { + t.Fatalf("Eval: %v", err) + } + got := len(matches) == 1 + if got != tc.want { + t.Fatalf("command %q: matched=%v want=%v", tc.command, got, tc.want) + } + }) + } +} + +func TestCanonicalizePathUnit(t *testing.T) { + cases := map[string]string{ + "/etc/numbat/rules/d0/d1/d2/d3/../../../../protect_numbat.yaml": "/etc/numbat/rules/protect_numbat.yaml", + "/proc/self/root/etc/numbat/rules/x.yaml": "/etc/numbat/rules/x.yaml", + "/proc/thread-self/root/usr/local/bin/numbat": "/usr/local/bin/numbat", + "/proc/12/root/../root/usr/local/bin/numbat": "/usr/local/bin/numbat", + "/usr/local/bin//numbat": "/usr/local/bin/numbat", + "": "", + "/proc/self/root": "/", + } + for in, want := range cases { + if got := model.CanonicalizePath(in); got != want { + t.Fatalf("CanonicalizePath(%q) = %q, want %q", in, got, want) + } + } +} diff --git a/internal/rule/engine.go b/internal/rule/engine.go index a3b08a7..2a9eab7 100644 --- a/internal/rule/engine.go +++ b/internal/rule/engine.go @@ -119,6 +119,18 @@ func newEnv() (*cel.Env, error) { ext.Lists(), cel.Variable("event", cel.MapType(cel.StringType, cel.DynType)), cel.Variable(shellCommandsVariable, cel.ListType(cel.ObjectType("rule.ShellCommand"))), + cel.Function("canonical_path", + cel.Overload("canonical_path_string", + []*cel.Type{cel.StringType}, cel.StringType, + cel.UnaryBinding(func(arg ref.Val) ref.Val { + s, ok := arg.Value().(string) + if !ok { + return types.MaybeNoSuchOverloadErr(arg) + } + return types.String(model.CanonicalizePath(s)) + }), + ), + ), ) } From efe7c3e43ead71b01d10efb29c9c802908d47223 Mon Sep 17 00:00:00 2001 From: ronheichman Date: Sat, 29 Aug 2026 00:29:35 +0000 Subject: [PATCH 2/9] fix(rule): tighten canonical path contract --- docs/rules.md | 8 +++--- internal/model/event.go | 26 ------------------ internal/rule/canonical_path_test.go | 40 +++++++++++++++------------- internal/rule/engine.go | 24 +++++++++++++---- 4 files changed, 44 insertions(+), 54 deletions(-) diff --git a/docs/rules.md b/docs/rules.md index 08f09b8..ef2cfa8 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -149,11 +149,9 @@ Common CEL operations include: `matches` uses RE2 regular expressions. CEL string literals require their own escaping; for example, a literal dot is written as `"\\.env"`. -`canonical_path(p)` returns the real target a path names: it applies -`path.Clean` at any depth and strips a leading `/proc//root` -prefix. Use it to match a protected path by its resolved location instead of -enumerating `.`, `..`, `//`, and `/proc` disguises. It resolves lexical -traversal only and does not dereference symlinks. +`canonical_path(p)` normalizes path separators, `.`, `..`, and duplicate `/` +segments. It also treats a leading `/proc//root` as `/`. +It does not access the filesystem or resolve other symbolic links. Action types are alternatives, not layers. A recognized shell action is a `command.exec`, not both a `tool.call` and a `command.exec`; file and network diff --git a/internal/model/event.go b/internal/model/event.go index 48fccb5..20bee93 100644 --- a/internal/model/event.go +++ b/internal/model/event.go @@ -7,7 +7,6 @@ import ( "crypto/sha256" "encoding/hex" "path" - "regexp" "sort" "strings" ) @@ -527,31 +526,6 @@ func filepathClean(p string) string { return path.Clean(NormalizeEventPath(p)) } -// procRootPrefix matches a leading /proc//root on an -// already path.Clean-ed path. A process reaches another mount namespace's file -// through this magic symlink, so /proc/self/root/etc/x names the same object as -// /etc/x. The remainder is captured so the prefix can be re-rooted to "/". -var procRootPrefix = regexp.MustCompile(`^/proc/(?:self|thread-self|[0-9]+)/root(/.*)?$`) - -// CanonicalizePath returns the real filesystem target a path names. It cleans -// the path (collapsing ., .., and duplicate separators at any depth) and then -// strips a /proc//root prefix, re-rooting it to "/". An -// empty input stays empty. It resolves lexical traversal only; it does not -// dereference symlinks, which are not visible in a static command or event. -func CanonicalizePath(p string) string { - clean := filepathClean(p) - if clean == "" { - return "" - } - if m := procRootPrefix.FindStringSubmatch(clean); m != nil { - if m[1] == "" { - return "/" - } - return m[1] - } - return clean -} - // MergeTags returns the sorted, de-duplicated union of two tag sets. func MergeTags(a, b []string) []string { set := make(map[string]struct{}, len(a)+len(b)) diff --git a/internal/rule/canonical_path_test.go b/internal/rule/canonical_path_test.go index 0b5eba0..df99377 100644 --- a/internal/rule/canonical_path_test.go +++ b/internal/rule/canonical_path_test.go @@ -23,9 +23,15 @@ func TestCanonicalPathCollapsesTraversalForFileEvents(t *testing.T) { {"deep_traversal_depth4", "/etc/numbat/rules/d0/d1/d2/d3/../../../../protect_numbat.yaml", true}, {"proc_root_prefix", "/proc/self/root/etc/numbat/rules/protect_numbat.yaml", true}, {"proc_root_pid_traversal", "/proc/4321/root/etc/numbat/rules/d0/d1/../../protect_numbat.yaml", true}, + {"proc_root_parent_traversal", "/proc/self/root/../etc/numbat/rules/protect_numbat.yaml", true}, + {"proc_root_dot_prefix", "/proc/./self/root/etc/numbat/rules/protect_numbat.yaml", true}, {"duplicate_slash", "/etc/numbat//rules/protect_numbat.yaml", true}, + {"windows_separators", `\etc\numbat\rules\protect_numbat.yaml`, true}, + {"different_proc_entry", "/proc/not-a-pid/root/etc/numbat/rules/protect_numbat.yaml", false}, + {"similar_proc_entry", "/proc/self/rooted/etc/numbat/rules/protect_numbat.yaml", false}, {"unprotected_sibling", "/etc/numbat/rules.example/protect_numbat.yaml", false}, {"escapes_out", "/etc/numbat/rules/../ordinary.txt", false}, + {"whitespace_is_path_content", " /etc/numbat/rules/protect_numbat.yaml ", false}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -42,6 +48,21 @@ func TestCanonicalPathCollapsesTraversalForFileEvents(t *testing.T) { } } +func TestCanonicalPathKeepsMissingPathEmpty(t *testing.T) { + eng := mustEngine(t, Rule{ + ID: "t.empty_path", + Severity: model.SeverityLow, + Expr: `canonical_path(event.file_path) == ""`, + }) + matches, err := eng.Eval(model.Event{EventType: model.EventCommandExec}) + if err != nil { + t.Fatal(err) + } + if len(matches) != 1 { + t.Fatalf("matches = %d, want 1", len(matches)) + } +} + func TestCanonicalPathCollapsesTraversalForShellArgv(t *testing.T) { eng := mustEngine(t, Rule{ ID: "t.protect_rule_rm", @@ -56,7 +77,7 @@ func TestCanonicalPathCollapsesTraversalForShellArgv(t *testing.T) { }{ {"plain", "rm -f /usr/local/bin/numbat", true}, {"deep_traversal_depth5", "rm -f /usr/local/bin/d0/d1/d2/d3/d4/../../../../../numbat", true}, - {"proc_root", "rm -f /proc/self/root/usr/local/bin/numbat", true}, + {"proc_root", "rm -f /proc/thread-self/root/usr/local/bin/numbat", true}, {"benign_other", "rm -f /tmp/numbat", false}, } for _, tc := range cases { @@ -73,20 +94,3 @@ func TestCanonicalPathCollapsesTraversalForShellArgv(t *testing.T) { }) } } - -func TestCanonicalizePathUnit(t *testing.T) { - cases := map[string]string{ - "/etc/numbat/rules/d0/d1/d2/d3/../../../../protect_numbat.yaml": "/etc/numbat/rules/protect_numbat.yaml", - "/proc/self/root/etc/numbat/rules/x.yaml": "/etc/numbat/rules/x.yaml", - "/proc/thread-self/root/usr/local/bin/numbat": "/usr/local/bin/numbat", - "/proc/12/root/../root/usr/local/bin/numbat": "/usr/local/bin/numbat", - "/usr/local/bin//numbat": "/usr/local/bin/numbat", - "": "", - "/proc/self/root": "/", - } - for in, want := range cases { - if got := model.CanonicalizePath(in); got != want { - t.Fatalf("CanonicalizePath(%q) = %q, want %q", in, got, want) - } - } -} diff --git a/internal/rule/engine.go b/internal/rule/engine.go index 2a9eab7..cd2b186 100644 --- a/internal/rule/engine.go +++ b/internal/rule/engine.go @@ -3,7 +3,9 @@ package rule import ( "errors" "fmt" + "path" "reflect" + "regexp" "strings" "time" "unicode" @@ -51,6 +53,22 @@ type compiledExpression struct { const contentRuleCostLimit uint64 = 10_000_000 +var procRootPath = regexp.MustCompile(`^/proc/(?:self|thread-self|[0-9]+)/root(?:/+|$)`) + +func canonicalPath(value string) string { + value = model.NormalizeEventPath(value) + if value == "" { + return "" + } + clean := path.Clean(value) + for _, candidate := range []string{value, clean} { + if prefix := procRootPath.FindStringIndex(candidate); prefix != nil { + return path.Clean("/" + candidate[prefix[1]:]) + } + } + return clean +} + // SequenceRule is a compiled sequence rule ready for per-step evaluation. It // distills the validated spec (window, cap) next to the compiled step // programs so a tracker never re-parses YAML fields on the hot path. @@ -123,11 +141,7 @@ func newEnv() (*cel.Env, error) { cel.Overload("canonical_path_string", []*cel.Type{cel.StringType}, cel.StringType, cel.UnaryBinding(func(arg ref.Val) ref.Val { - s, ok := arg.Value().(string) - if !ok { - return types.MaybeNoSuchOverloadErr(arg) - } - return types.String(model.CanonicalizePath(s)) + return types.String(canonicalPath(string(arg.(types.String)))) }), ), ), From 45aa70f8edf1000f85c5b6932730305df8922cb3 Mon Sep 17 00:00:00 2001 From: ronheichman Date: Sat, 29 Aug 2026 00:42:41 +0000 Subject: [PATCH 3/9] fix(rule): resolve nested proc root prefixes --- docs/rules.md | 2 +- internal/rule/canonical_path_test.go | 17 +++++++++++++++++ internal/rule/engine.go | 17 ++++++++++++----- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/docs/rules.md b/docs/rules.md index ef2cfa8..7b3559b 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -150,7 +150,7 @@ Common CEL operations include: escaping; for example, a literal dot is written as `"\\.env"`. `canonical_path(p)` normalizes path separators, `.`, `..`, and duplicate `/` -segments. It also treats a leading `/proc//root` as `/`. +segments. It treats each leading `/proc//root` as `/`. It does not access the filesystem or resolve other symbolic links. Action types are alternatives, not layers. A recognized shell action is a diff --git a/internal/rule/canonical_path_test.go b/internal/rule/canonical_path_test.go index df99377..4a213e8 100644 --- a/internal/rule/canonical_path_test.go +++ b/internal/rule/canonical_path_test.go @@ -22,6 +22,7 @@ func TestCanonicalPathCollapsesTraversalForFileEvents(t *testing.T) { {"shallow_traversal", "/etc/numbat/rules/x/../protect_numbat.yaml", true}, {"deep_traversal_depth4", "/etc/numbat/rules/d0/d1/d2/d3/../../../../protect_numbat.yaml", true}, {"proc_root_prefix", "/proc/self/root/etc/numbat/rules/protect_numbat.yaml", true}, + {"nested_proc_root_prefix", "/proc/self/root/proc/thread-self/root/etc/numbat/rules/protect_numbat.yaml", true}, {"proc_root_pid_traversal", "/proc/4321/root/etc/numbat/rules/d0/d1/../../protect_numbat.yaml", true}, {"proc_root_parent_traversal", "/proc/self/root/../etc/numbat/rules/protect_numbat.yaml", true}, {"proc_root_dot_prefix", "/proc/./self/root/etc/numbat/rules/protect_numbat.yaml", true}, @@ -63,6 +64,22 @@ func TestCanonicalPathKeepsMissingPathEmpty(t *testing.T) { } } +func TestCanonicalPathRejectsNonStringEventField(t *testing.T) { + eng := mustEngine(t, Rule{ + ID: "t.non_string_path", + Severity: model.SeverityLow, + Expr: `canonical_path(event.exit_code) == ""`, + }) + exitCode := 1 + matches, err := eng.Eval(model.Event{EventType: model.EventCommandExec, ExitCode: &exitCode}) + if err == nil { + t.Fatal("Eval error = nil, want type error") + } + if len(matches) != 0 { + t.Fatalf("matches = %d, want 0", len(matches)) + } +} + func TestCanonicalPathCollapsesTraversalForShellArgv(t *testing.T) { eng := mustEngine(t, Rule{ ID: "t.protect_rule_rm", diff --git a/internal/rule/engine.go b/internal/rule/engine.go index cd2b186..e0380ce 100644 --- a/internal/rule/engine.go +++ b/internal/rule/engine.go @@ -60,13 +60,20 @@ func canonicalPath(value string) string { if value == "" { return "" } - clean := path.Clean(value) - for _, candidate := range []string{value, clean} { - if prefix := procRootPath.FindStringIndex(candidate); prefix != nil { - return path.Clean("/" + candidate[prefix[1]:]) + for { + if prefix := procRootPath.FindStringIndex(value); prefix != nil { + if prefix[1] == len(value) { + return "/" + } + value = value[prefix[1]-1:] + continue + } + clean := path.Clean(value) + if clean == value { + return clean } + value = clean } - return clean } // SequenceRule is a compiled sequence rule ready for per-step evaluation. It From 384b33f0171915b1cf331da450677a260de5cbc6 Mon Sep 17 00:00:00 2001 From: ronheichman Date: Sat, 29 Aug 2026 00:57:47 +0000 Subject: [PATCH 4/9] fix(rule): tighten canonical path boundaries --- docs/rules.md | 3 ++- internal/rule/canonical_path_test.go | 6 +++++- internal/rule/engine.go | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/rules.md b/docs/rules.md index 7b3559b..34a2e49 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -151,7 +151,8 @@ escaping; for example, a literal dot is written as `"\\.env"`. `canonical_path(p)` normalizes path separators, `.`, `..`, and duplicate `/` segments. It treats each leading `/proc//root` as `/`. -It does not access the filesystem or resolve other symbolic links. +Relative paths remain relative. It does not access the filesystem or resolve +other symbolic links. Action types are alternatives, not layers. A recognized shell action is a `command.exec`, not both a `tool.call` and a `command.exec`; file and network diff --git a/internal/rule/canonical_path_test.go b/internal/rule/canonical_path_test.go index 4a213e8..c216dbd 100644 --- a/internal/rule/canonical_path_test.go +++ b/internal/rule/canonical_path_test.go @@ -28,8 +28,12 @@ func TestCanonicalPathCollapsesTraversalForFileEvents(t *testing.T) { {"proc_root_dot_prefix", "/proc/./self/root/etc/numbat/rules/protect_numbat.yaml", true}, {"duplicate_slash", "/etc/numbat//rules/protect_numbat.yaml", true}, {"windows_separators", `\etc\numbat\rules\protect_numbat.yaml`, true}, + {"zero_is_not_a_pid", "/proc/0/root/etc/numbat/rules/protect_numbat.yaml", false}, + {"zero_padded_pid", "/proc/04321/root/etc/numbat/rules/protect_numbat.yaml", false}, {"different_proc_entry", "/proc/not-a-pid/root/etc/numbat/rules/protect_numbat.yaml", false}, {"similar_proc_entry", "/proc/self/rooted/etc/numbat/rules/protect_numbat.yaml", false}, + {"relative_path_stays_relative", "etc/numbat/rules/protect_numbat.yaml", false}, + {"leading_parent_stays_relative", "../etc/numbat/rules/protect_numbat.yaml", false}, {"unprotected_sibling", "/etc/numbat/rules.example/protect_numbat.yaml", false}, {"escapes_out", "/etc/numbat/rules/../ordinary.txt", false}, {"whitespace_is_path_content", " /etc/numbat/rules/protect_numbat.yaml ", false}, @@ -104,7 +108,7 @@ func TestCanonicalPathCollapsesTraversalForShellArgv(t *testing.T) { if err != nil { t.Fatalf("Eval: %v", err) } - got := len(matches) == 1 + got := len(matches) == 1 && matches[0].EnforcementMatch if got != tc.want { t.Fatalf("command %q: matched=%v want=%v", tc.command, got, tc.want) } diff --git a/internal/rule/engine.go b/internal/rule/engine.go index e0380ce..40e8b3d 100644 --- a/internal/rule/engine.go +++ b/internal/rule/engine.go @@ -53,7 +53,7 @@ type compiledExpression struct { const contentRuleCostLimit uint64 = 10_000_000 -var procRootPath = regexp.MustCompile(`^/proc/(?:self|thread-self|[0-9]+)/root(?:/+|$)`) +var procRootPath = regexp.MustCompile(`^/proc/(?:self|thread-self|[1-9][0-9]*)/root(?:/+|$)`) func canonicalPath(value string) string { value = model.NormalizeEventPath(value) From 2b092d2dc62f90b83db5d14a9a25601aed89f224 Mon Sep 17 00:00:00 2001 From: ronheichman Date: Mon, 31 Aug 2026 12:57:58 +0000 Subject: [PATCH 5/9] Fix protected path alias enforcement --- rules/catalog_test.go | 1 + ...aa46e5a7cf335aa111e435a2f7dbd6e83e23fcbf7.pb | Bin 10766 -> 0 bytes ...cb43c08579691ec863b9c284f2e3849a569e998ec.pb | Bin 17078 -> 0 bytes ...8621577f5e4dfa64f4697de12a8b48d8894ead92c.pb | Bin 0 -> 1046 bytes ...cb761904a2748466676c7cf28984c30426bbb84fc.pb | Bin 0 -> 17359 bytes ...1c7db5a6ab416c693062093e9772decde2de31556.pb | Bin 915 -> 0 bytes ...5a12927ae303b766563660e10f9a6088495bfdec2.pb | Bin 0 -> 10925 bytes rules/persistence/ssh_authorized_keys.yaml | 6 +++--- .../ssh_authorized_keys_command.yaml | 12 ++++++------ rules/privilege/sudoers_tamper.yaml | 10 +++++----- rules/release_precision_policy_test.go | 1 + 11 files changed, 16 insertions(+), 14 deletions(-) delete mode 100644 rules/internal/checked/158bc6790b156ee22867286aa46e5a7cf335aa111e435a2f7dbd6e83e23fcbf7.pb delete mode 100644 rules/internal/checked/15aec85709b643c408fb7b9cb43c08579691ec863b9c284f2e3849a569e998ec.pb create mode 100644 rules/internal/checked/311dd9c749c7038eccae6818621577f5e4dfa64f4697de12a8b48d8894ead92c.pb create mode 100644 rules/internal/checked/46c5d94e10cca2caa793940cb761904a2748466676c7cf28984c30426bbb84fc.pb delete mode 100644 rules/internal/checked/68d870135b729e90352abf91c7db5a6ab416c693062093e9772decde2de31556.pb create mode 100644 rules/internal/checked/e6634233a808b79f131e95b5a12927ae303b766563660e10f9a6088495bfdec2.pb diff --git a/rules/catalog_test.go b/rules/catalog_test.go index 13f7784..5304a3c 100644 --- a/rules/catalog_test.go +++ b/rules/catalog_test.go @@ -458,6 +458,7 @@ func TestPrivilegeRules(t *testing.T) { eng := builtinEngine(t) runCases(t, eng, []ruleCase{ {"sudoers file write", write("/etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, + {"sudoers dot-segment alias", write("/etc/sudoers.d/./agent"), "privilege.sudoers_tamper"}, {"append nopasswd sudoers", cmd("echo 'agent ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, {"tee sudoers", cmd("printf 'agent ALL=(ALL) NOPASSWD:ALL' | tee -a /etc/sudoers"), "privilege.sudoers_tamper"}, {"visudo command", cmd("EDITOR=tee visudo"), "privilege.sudoers_tamper"}, diff --git a/rules/internal/checked/158bc6790b156ee22867286aa46e5a7cf335aa111e435a2f7dbd6e83e23fcbf7.pb b/rules/internal/checked/158bc6790b156ee22867286aa46e5a7cf335aa111e435a2f7dbd6e83e23fcbf7.pb deleted file mode 100644 index 6fd3f164185fe6e05a8c46eae8471e10e50d0839..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10766 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 D-wQX9 diff --git a/rules/internal/checked/15aec85709b643c408fb7b9cb43c08579691ec863b9c284f2e3849a569e998ec.pb b/rules/internal/checked/15aec85709b643c408fb7b9cb43c08579691ec863b9c284f2e3849a569e998ec.pb deleted file mode 100644 index 046c414b52e02ea1dcf0cb0950c45e8b16d71b73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17078 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 G$NvIM@l|^O diff --git a/rules/internal/checked/311dd9c749c7038eccae6818621577f5e4dfa64f4697de12a8b48d8894ead92c.pb b/rules/internal/checked/311dd9c749c7038eccae6818621577f5e4dfa64f4697de12a8b48d8894ead92c.pb new file mode 100644 index 0000000000000000000000000000000000000000..a70cae18707679bdaa10f4c9aba740c66bc312cf GIT binary patch literal 1046 zcmaJr$fa$)d$ON=~mjf)9v206v2vu(o*Sz_7bMaY&W=@)FfT0 z1Ybn-#h*~91@XuDI|LDY6MS{DP3^XnKIHr6oP6iZxeV5jgwv|R``ig|7ReZq6o0+X zx;~yj3cgORnapt=+hkp1j|CmW4?Nr1#TrVT#f>3^f#iEEFgx5ol}sS@s(5WUK6gnx zpaRYkwd?K(yAVUC?J^eJzs3G64N4E*j((BxY`? z8c37>=dKRMZv+Q>oB$+Ama1*L%je#BcEHK83sHYemmqQ zq-VxO^3+-KOq6P{JDiR;M7~OpAx9EDl%|*L^7iz*aI^S$<=L_z?(|#J>(7_hR@YV@ zJ$bquM*ebG8gi9Fz2o+{zOk)u-3j$|pL_nP;kj-Q>V4+vC#OBn?z4cOc{U!*zT9dJ z2bJ~BIp6Q-Y(MC@p8b|v#w&i{)1@N$CP9uKN6o62Hk%zZt6kl!33+1Bt&>jU>sLK@ z*JHhhEMRfZ!=BQRQ7Y?$;;f$S*y8cC!1a9NLaoJ_?-G9D?Duw9&@y>m`;uXpoJJp8OXf&&w zSxssZRGj)Ny_Y}8lSw4Oh`9!F(F8U ZPZ4F|b3{2fiYO0XA}YWiN$3G?{{x|L_g4S_ literal 0 HcmV?d00001 diff --git a/rules/internal/checked/46c5d94e10cca2caa793940cb761904a2748466676c7cf28984c30426bbb84fc.pb b/rules/internal/checked/46c5d94e10cca2caa793940cb761904a2748466676c7cf28984c30426bbb84fc.pb new file mode 100644 index 0000000000000000000000000000000000000000..72aef6bb7732203e96285022f03ffd080e8f4b2c GIT binary patch literal 17359 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 diff --git a/rules/internal/checked/e6634233a808b79f131e95b5a12927ae303b766563660e10f9a6088495bfdec2.pb b/rules/internal/checked/e6634233a808b79f131e95b5a12927ae303b766563660e10f9a6088495bfdec2.pb new file mode 100644 index 0000000000000000000000000000000000000000..8c2634641e9e4749115245e484dde68bbb409c26 GIT binary patch literal 10925 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 PL&c9Q7UDlSK;-`bv+-6Y literal 0 HcmV?d00001 diff --git a/rules/persistence/ssh_authorized_keys.yaml b/rules/persistence/ssh_authorized_keys.yaml index 58869be..8e34104 100644 --- a/rules/persistence/ssh_authorized_keys.yaml +++ b/rules/persistence/ssh_authorized_keys.yaml @@ -1,5 +1,5 @@ id: persistence.ssh_authorized_keys -version: "1.2" +version: "1.3" enabled: true title: SSH authorized_keys modification description: |- @@ -9,7 +9,7 @@ severity: critical expr: |- event.event_type == "file.write" && ( - event.file_path.matches("(?i)^(~|\\$HOME|\\$env:(USERPROFILE|HOME)|%USERPROFILE%|/home/[^/]+|/Users/[^/]+|/root|/var/root|/private/var/root|[A-Z]:/Users/[^/]+)/\\.ssh/authorized_keys2?$") || - event.file_path.matches("(?i)^([A-Z]:/ProgramData|\\$env:ProgramData|%ProgramData%)/ssh/administrators_authorized_keys$") + canonical_path(event.file_path).matches("(?i)^(~|\\$HOME|\\$env:(USERPROFILE|HOME)|%USERPROFILE%|/home/[^/]+|/Users/[^/]+|/root|/var/root|/private/var/root|[A-Z]:/Users/[^/]+)/\\.ssh/authorized_keys2?$") || + canonical_path(event.file_path).matches("(?i)^([A-Z]:/ProgramData|\\$env:ProgramData|%ProgramData%)/ssh/administrators_authorized_keys$") ) tags: [attack.t1098.004] diff --git a/rules/persistence/ssh_authorized_keys_command.yaml b/rules/persistence/ssh_authorized_keys_command.yaml index 1e144d4..67fb7a6 100644 --- a/rules/persistence/ssh_authorized_keys_command.yaml +++ b/rules/persistence/ssh_authorized_keys_command.yaml @@ -1,5 +1,5 @@ id: persistence.ssh_authorized_keys_command -version: "1.6" +version: "1.7" enabled: true title: Command targeted SSH authorized_keys description: |- @@ -11,12 +11,12 @@ expr: |- shell_commands.exists(command, command.redirects.exists(redirect, redirect.op in ["write", "append"] && - redirect.target.matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))[\\\\/]\\.ssh[\\\\/]authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:[\\\\/]Users[\\\\/][^\\\\/]+)[\\\\/]\\.ssh[\\\\/]authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:[\\\\/]ProgramData)[\\\\/]ssh[\\\\/]administrators_authorized_keys)$") + canonical_path(redirect.target).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\\.ssh/authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\\.ssh/authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$") ) || ( command.name.matches("(?i)^(tee|truncate|rm|sed)$") && command.argv.exists(arg, - arg.matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))[\\\\/]\\.ssh[\\\\/]authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:[\\\\/]Users[\\\\/][^\\\\/]+)[\\\\/]\\.ssh[\\\\/]authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:[\\\\/]ProgramData)[\\\\/]ssh[\\\\/]administrators_authorized_keys)$") + canonical_path(arg).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\\.ssh/authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\\.ssh/authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$") ) && ( !command.name.matches("(?i)^sed$") || @@ -27,7 +27,7 @@ expr: |- command.name.matches("(?i)^(set-content|add-content|clear-content|out-file|remove-item|ri|new-item)$") && lists.range(command.argv.size()).exists(i, i > 0 && - command.argv[i].matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))[\\\\/]\\.ssh[\\\\/]authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:[\\\\/]Users[\\\\/][^\\\\/]+)[\\\\/]\\.ssh[\\\\/]authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:[\\\\/]ProgramData)[\\\\/]ssh[\\\\/]administrators_authorized_keys)$") && + canonical_path(command.argv[i]).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\\.ssh/authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\\.ssh/authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$") && ( i == 1 || command.argv[i - 1].matches("(?i)^-(path|literalpath|filepath)$") || @@ -42,10 +42,10 @@ expr: |- command.name.matches("(?i)^(cp|mv|install|copy-item|move-item)$") && command.argv.size() > 2 && ( - command.argv[command.argv.size() - 1].matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))[\\\\/]\\.ssh[\\\\/]authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:[\\\\/]Users[\\\\/][^\\\\/]+)[\\\\/]\\.ssh[\\\\/]authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:[\\\\/]ProgramData)[\\\\/]ssh[\\\\/]administrators_authorized_keys)$") || + canonical_path(command.argv[command.argv.size() - 1]).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\\.ssh/authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\\.ssh/authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$") || lists.range(command.argv.size() - 1).exists(i, command.argv[i].matches("(?i)^-destination$") && - command.argv[i + 1].matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))[\\\\/]\\.ssh[\\\\/]authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:[\\\\/]Users[\\\\/][^\\\\/]+)[\\\\/]\\.ssh[\\\\/]authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:[\\\\/]ProgramData)[\\\\/]ssh[\\\\/]administrators_authorized_keys)$") + canonical_path(command.argv[i + 1]).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\\.ssh/authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\\.ssh/authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$") ) ) ) diff --git a/rules/privilege/sudoers_tamper.yaml b/rules/privilege/sudoers_tamper.yaml index 5e1a28f..5ac3c33 100644 --- a/rules/privilege/sudoers_tamper.yaml +++ b/rules/privilege/sudoers_tamper.yaml @@ -1,5 +1,5 @@ id: privilege.sudoers_tamper -version: "1.3" +version: "1.4" enabled: true title: Agent targeted sudoers policy for modification description: |- @@ -10,7 +10,7 @@ severity: high expr: |- ( (event.event_type == "file.write" || event.event_type == "file.delete") && - event.file_path.matches("^/etc/sudoers$|^/etc/sudoers\\.d/[^/]+$") + canonical_path(event.file_path).matches("^/etc/sudoers$|^/etc/sudoers\\.d/[^/]+$") ) || ( (event.event_type == "command.exec" || (event.source_type == "otel" && event.event_type == "command.result")) && shell_commands.exists(command, @@ -33,7 +33,7 @@ expr: |- ( command.argv[i].matches("(?i)^(-f|--file)$") && i + 1 < command.argv.size() && - command.argv[i + 1].matches("(?i)^/etc/sudoers(\\.d/[^/]+)?$") + canonical_path(command.argv[i + 1]).matches("(?i)^/etc/sudoers(\\.d/[^/]+)?$") ) ) ) @@ -41,11 +41,11 @@ expr: |- ) || command.redirects.exists(redirect, redirect.op in ["write", "append"] && - redirect.target.matches("(?i)^/etc/sudoers(\\.d/[^/]+)?$") + canonical_path(redirect.target).matches("(?i)^/etc/sudoers(\\.d/[^/]+)?$") ) || lists.range(command.argv.size()).exists(i, i > 0 && - command.argv[i].matches("(?i)^/etc/sudoers(\\.d/[^/]+)?$") && + canonical_path(command.argv[i]).matches("(?i)^/etc/sudoers(\\.d/[^/]+)?$") && ( command.name.matches("(?i)^(rm|truncate|tee)$") || ( diff --git a/rules/release_precision_policy_test.go b/rules/release_precision_policy_test.go index b92bf88..b8648b4 100644 --- a/rules/release_precision_policy_test.go +++ b/rules/release_precision_policy_test.go @@ -59,6 +59,7 @@ func TestReleasePrecisionPersistencePolicy(t *testing.T) { {"repository authorized-keys fixture stays quiet", cmd("printf key >> /repo/testdata/.ssh/authorized_keys"), ""}, {"home-shaped authorized-keys fixture stays quiet", cmd("printf key >> /repo/home/dev/.ssh/authorized_keys"), ""}, {"home authorized-keys target", cmd("printf key >> ~/.ssh/authorized_keys"), "persistence.ssh_authorized_keys_command"}, + {"home authorized-keys duplicate separator", cmd("printf key >> /home/dev/.ssh//authorized_keys"), "persistence.ssh_authorized_keys_command"}, {"authorized-keys PowerShell WhatIf still records intent", cmd("Set-Content ~/.ssh/authorized_keys key -WhatIf"), "persistence.ssh_authorized_keys_command"}, {"repository dotfile stays quiet", write("/repo/.zshrc"), ""}, {"repository dotfile command stays quiet", cmd("printf x > /repo/.zshrc"), ""}, From 1b002ba65da57576bd4ebac5b511341be1587956 Mon Sep 17 00:00:00 2001 From: ronheichman Date: Mon, 31 Aug 2026 18:48:58 +0000 Subject: [PATCH 6/9] fix(rule): canonicalize proc task root aliases --- docs/rules.md | 7 ++++--- internal/rule/canonical_path_test.go | 13 +++++++++++++ internal/rule/engine.go | 3 ++- rules/catalog_test.go | 2 ++ ...cb761904a2748466676c7cf28984c30426bbb84fc.pb | Bin 17359 -> 0 bytes ...a9f128d1107084d3377e2f43c199139152fc28204.pb | Bin 0 -> 18690 bytes rules/privilege/sudoers_tamper.yaml | 9 ++++++++- rules/release_precision_policy_test.go | 4 ++++ 8 files changed, 33 insertions(+), 5 deletions(-) delete mode 100644 rules/internal/checked/46c5d94e10cca2caa793940cb761904a2748466676c7cf28984c30426bbb84fc.pb create mode 100644 rules/internal/checked/b20be62ab63e4da2e7ce3d6a9f128d1107084d3377e2f43c199139152fc28204.pb diff --git a/docs/rules.md b/docs/rules.md index 34a2e49..51152b6 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -140,6 +140,7 @@ Common CEL operations include: | Boolean logic | `a && b`, `a || b`, `!a` | | Membership | `value in ["a", "b"]` | | String tests | `contains`, `startsWith`, `endsWith`, `matches` | +| String slicing | `substring(start)`, `substring(start, end)` | | List predicates | `exists`, `all`, `exists_one` | | List range | `items.slice(start, end)` | | Integer indexes | `lists.range(n).exists(i, ...)` | @@ -150,9 +151,9 @@ Common CEL operations include: escaping; for example, a literal dot is written as `"\\.env"`. `canonical_path(p)` normalizes path separators, `.`, `..`, and duplicate `/` -segments. It treats each leading `/proc//root` as `/`. -Relative paths remain relative. It does not access the filesystem or resolve -other symbolic links. +segments. It treats each leading `/proc//root` or +`/proc//task//root` as `/`. Relative paths remain relative. It +does not access the filesystem or resolve other symbolic links. Action types are alternatives, not layers. A recognized shell action is a `command.exec`, not both a `tool.call` and a `command.exec`; file and network diff --git a/internal/rule/canonical_path_test.go b/internal/rule/canonical_path_test.go index c216dbd..90b236c 100644 --- a/internal/rule/canonical_path_test.go +++ b/internal/rule/canonical_path_test.go @@ -22,7 +22,12 @@ func TestCanonicalPathCollapsesTraversalForFileEvents(t *testing.T) { {"shallow_traversal", "/etc/numbat/rules/x/../protect_numbat.yaml", true}, {"deep_traversal_depth4", "/etc/numbat/rules/d0/d1/d2/d3/../../../../protect_numbat.yaml", true}, {"proc_root_prefix", "/proc/self/root/etc/numbat/rules/protect_numbat.yaml", true}, + {"proc_task_root_pid", "/proc/4321/task/8765/root/etc/numbat/rules/protect_numbat.yaml", true}, + {"proc_task_root_self", "/proc/self/task/8765/root/etc/numbat/rules/protect_numbat.yaml", true}, + {"proc_task_root_dot_segment", "/proc/4321/task/./8765/root/etc/numbat/rules/protect_numbat.yaml", true}, + {"proc_task_root_parent_traversal", "/proc/4321/task/8765/root/../etc/numbat/rules/protect_numbat.yaml", true}, {"nested_proc_root_prefix", "/proc/self/root/proc/thread-self/root/etc/numbat/rules/protect_numbat.yaml", true}, + {"nested_proc_task_root_prefix", "/proc/4321/task/8765/root/proc/self/task/8765/root/etc/numbat/rules/protect_numbat.yaml", true}, {"proc_root_pid_traversal", "/proc/4321/root/etc/numbat/rules/d0/d1/../../protect_numbat.yaml", true}, {"proc_root_parent_traversal", "/proc/self/root/../etc/numbat/rules/protect_numbat.yaml", true}, {"proc_root_dot_prefix", "/proc/./self/root/etc/numbat/rules/protect_numbat.yaml", true}, @@ -30,6 +35,13 @@ func TestCanonicalPathCollapsesTraversalForFileEvents(t *testing.T) { {"windows_separators", `\etc\numbat\rules\protect_numbat.yaml`, true}, {"zero_is_not_a_pid", "/proc/0/root/etc/numbat/rules/protect_numbat.yaml", false}, {"zero_padded_pid", "/proc/04321/root/etc/numbat/rules/protect_numbat.yaml", false}, + {"task_zero_tid", "/proc/4321/task/0/root/etc/numbat/rules/protect_numbat.yaml", false}, + {"task_zero_padded_tid", "/proc/4321/task/08765/root/etc/numbat/rules/protect_numbat.yaml", false}, + {"task_non_numeric_tid", "/proc/4321/task/current/root/etc/numbat/rules/protect_numbat.yaml", false}, + {"task_self_is_not_a_tid", "/proc/4321/task/self/root/etc/numbat/rules/protect_numbat.yaml", false}, + {"thread_self_has_no_task_segment", "/proc/thread-self/task/8765/root/etc/numbat/rules/protect_numbat.yaml", false}, + {"missing_task_tid", "/proc/4321/task/root/etc/numbat/rules/protect_numbat.yaml", false}, + {"task_root_name_boundary", "/proc/4321/task/8765/rooted/etc/numbat/rules/protect_numbat.yaml", false}, {"different_proc_entry", "/proc/not-a-pid/root/etc/numbat/rules/protect_numbat.yaml", false}, {"similar_proc_entry", "/proc/self/rooted/etc/numbat/rules/protect_numbat.yaml", false}, {"relative_path_stays_relative", "etc/numbat/rules/protect_numbat.yaml", false}, @@ -99,6 +111,7 @@ func TestCanonicalPathCollapsesTraversalForShellArgv(t *testing.T) { {"plain", "rm -f /usr/local/bin/numbat", true}, {"deep_traversal_depth5", "rm -f /usr/local/bin/d0/d1/d2/d3/d4/../../../../../numbat", true}, {"proc_root", "rm -f /proc/thread-self/root/usr/local/bin/numbat", true}, + {"proc_task_root", "rm -f /proc/4321/task/8765/root/usr/local/bin/numbat", true}, {"benign_other", "rm -f /tmp/numbat", false}, } for _, tc := range cases { diff --git a/internal/rule/engine.go b/internal/rule/engine.go index 40e8b3d..4875522 100644 --- a/internal/rule/engine.go +++ b/internal/rule/engine.go @@ -53,7 +53,7 @@ type compiledExpression struct { const contentRuleCostLimit uint64 = 10_000_000 -var procRootPath = regexp.MustCompile(`^/proc/(?:self|thread-self|[1-9][0-9]*)/root(?:/+|$)`) +var procRootPath = regexp.MustCompile(`^/proc/(?:(?:self|[1-9][0-9]*)/task/[1-9][0-9]*|self|thread-self|[1-9][0-9]*)/root(?:/+|$)`) func canonicalPath(value string) string { value = model.NormalizeEventPath(value) @@ -142,6 +142,7 @@ func newEnv() (*cel.Env, error) { ext.ParseStructTags(true), ), ext.Lists(), + ext.Strings(), cel.Variable("event", cel.MapType(cel.StringType, cel.DynType)), cel.Variable(shellCommandsVariable, cel.ListType(cel.ObjectType("rule.ShellCommand"))), cel.Function("canonical_path", diff --git a/rules/catalog_test.go b/rules/catalog_test.go index 5304a3c..d2ad9e1 100644 --- a/rules/catalog_test.go +++ b/rules/catalog_test.go @@ -410,6 +410,7 @@ func TestPersistenceRules(t *testing.T) { {"authorized_keys write", write("/Users/dev/.ssh/authorized_keys"), "persistence.ssh_authorized_keys"}, {"Linux authorized_keys write", write("/home/dev/.ssh/authorized_keys"), "persistence.ssh_authorized_keys"}, + {"Linux authorized_keys proc task-root alias", write("/proc/4321/task/8765/root/home/dev/.ssh/authorized_keys"), "persistence.ssh_authorized_keys"}, {"Windows user authorized_keys write", write("C:/Users/dev/.ssh/authorized_keys"), "persistence.ssh_authorized_keys"}, {"Windows administrator authorized_keys write", write("C:/ProgramData/ssh/administrators_authorized_keys"), "persistence.ssh_authorized_keys"}, {"authorized_keys append", cmd("cat key.pub >> ~/.ssh/authorized_keys"), "persistence.ssh_authorized_keys_command"}, @@ -459,6 +460,7 @@ func TestPrivilegeRules(t *testing.T) { runCases(t, eng, []ruleCase{ {"sudoers file write", write("/etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, {"sudoers dot-segment alias", write("/etc/sudoers.d/./agent"), "privilege.sudoers_tamper"}, + {"sudoers proc task-root alias", write("/proc/4321/task/8765/root/etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, {"append nopasswd sudoers", cmd("echo 'agent ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, {"tee sudoers", cmd("printf 'agent ALL=(ALL) NOPASSWD:ALL' | tee -a /etc/sudoers"), "privilege.sudoers_tamper"}, {"visudo command", cmd("EDITOR=tee visudo"), "privilege.sudoers_tamper"}, diff --git a/rules/internal/checked/46c5d94e10cca2caa793940cb761904a2748466676c7cf28984c30426bbb84fc.pb b/rules/internal/checked/46c5d94e10cca2caa793940cb761904a2748466676c7cf28984c30426bbb84fc.pb deleted file mode 100644 index 72aef6bb7732203e96285022f03ffd080e8f4b2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17359 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 0 && ( - command.argv[i].matches("(?i)^(-f|--file)=/etc/sudoers(\\.d/[^/]+)?$") || + ( + command.argv[i].startsWith("-f=") && + canonical_path(command.argv[i].substring(3)).matches("^/etc/sudoers(\\.d/[^/]+)?$") + ) || + ( + command.argv[i].startsWith("--file=") && + canonical_path(command.argv[i].substring(7)).matches("^/etc/sudoers(\\.d/[^/]+)?$") + ) || ( command.argv[i].matches("(?i)^(-f|--file)$") && i + 1 < command.argv.size() && diff --git a/rules/release_precision_policy_test.go b/rules/release_precision_policy_test.go index b8648b4..ef36a18 100644 --- a/rules/release_precision_policy_test.go +++ b/rules/release_precision_policy_test.go @@ -25,8 +25,11 @@ func TestReleasePrecisionPrivilegePolicy(t *testing.T) { {"sudoers backup path stays quiet", cmd("rm /etc/sudoers.backup"), ""}, {"repository sudoers structured write stays quiet", write("/repo/etc/sudoers"), ""}, {"active sudoers structured delete", model.Event{EventType: model.EventFileDelete, FilePath: "/etc/sudoers"}, "privilege.sudoers_tamper"}, + {"sudoers redirect through proc task-root alias", cmd("printf policy > /proc/4321/task/8765/root/etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, {"sudoers PowerShell WhatIf still records intent", cmd("Set-Content /etc/sudoers test -WhatIf"), "privilege.sudoers_tamper"}, {"visudo against active policy", cmd("visudo --file=/etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, + {"visudo combined file through proc task-root alias", cmd("visudo --file=/proc/4321/task/8765/root/etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, + {"visudo combined file through traversal alias", cmd("visudo --file=/etc/numbat/../sudoers"), "privilege.sudoers_tamper"}, {"later visudo validation cannot suppress active policy edit", cmd("visudo --file=/etc/sudoers.d/agent; visudo -c"), "privilege.sudoers_tamper"}, {"later visudo help cannot suppress active policy edit", cmd("EDITOR=tee visudo; visudo --help"), "privilege.sudoers_tamper"}, {"quoted sudoers fixture is not a modification", cmd(`echo "tee /etc/sudoers.d/agent"`), ""}, @@ -60,6 +63,7 @@ func TestReleasePrecisionPersistencePolicy(t *testing.T) { {"home-shaped authorized-keys fixture stays quiet", cmd("printf key >> /repo/home/dev/.ssh/authorized_keys"), ""}, {"home authorized-keys target", cmd("printf key >> ~/.ssh/authorized_keys"), "persistence.ssh_authorized_keys_command"}, {"home authorized-keys duplicate separator", cmd("printf key >> /home/dev/.ssh//authorized_keys"), "persistence.ssh_authorized_keys_command"}, + {"home authorized-keys proc task-root alias", cmd("printf key >> /proc/self/task/8765/root/home/dev/.ssh/authorized_keys"), "persistence.ssh_authorized_keys_command"}, {"authorized-keys PowerShell WhatIf still records intent", cmd("Set-Content ~/.ssh/authorized_keys key -WhatIf"), "persistence.ssh_authorized_keys_command"}, {"repository dotfile stays quiet", write("/repo/.zshrc"), ""}, {"repository dotfile command stays quiet", cmd("printf x > /repo/.zshrc"), ""}, From 62817a9a3fe1b0e90b46a01c009da7dab45edc80 Mon Sep 17 00:00:00 2001 From: ronheichman Date: Mon, 31 Aug 2026 19:23:28 +0000 Subject: [PATCH 7/9] fix(rule): close protected-path operand gaps --- docs/rules.md | 3 +- internal/rule/canonical_path_test.go | 20 + internal/rule/engine.go | 20 +- ...f6be9adb8bc87fda5a3148c8f3254f21c3c1b73.pb | 870 +++++++++++++++++ ...ad0f093f09d1c7fe04c77e58f5e115d356f9ccb.pb | 899 ++++++++++++++++++ ...f128d1107084d3377e2f43c199139152fc28204.pb | Bin 18690 -> 0 bytes ...12927ae303b766563660e10f9a6088495bfdec2.pb | Bin 10925 -> 0 bytes .../ssh_authorized_keys_command.yaml | 149 ++- rules/privilege/sudoers_tamper.yaml | 102 +- rules/release_precision_policy_test.go | 28 + 10 files changed, 2068 insertions(+), 23 deletions(-) create mode 100644 rules/internal/checked/0378d920be6102465633215dcf6be9adb8bc87fda5a3148c8f3254f21c3c1b73.pb create mode 100644 rules/internal/checked/7f5aef9eb4a0b5fe04a85b8dcad0f093f09d1c7fe04c77e58f5e115d356f9ccb.pb delete mode 100644 rules/internal/checked/b20be62ab63e4da2e7ce3d6a9f128d1107084d3377e2f43c199139152fc28204.pb delete mode 100644 rules/internal/checked/e6634233a808b79f131e95b5a12927ae303b766563660e10f9a6088495bfdec2.pb diff --git a/docs/rules.md b/docs/rules.md index 51152b6..3c63fa2 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -153,7 +153,8 @@ escaping; for example, a literal dot is written as `"\\.env"`. `canonical_path(p)` normalizes path separators, `.`, `..`, and duplicate `/` segments. It treats each leading `/proc//root` or `/proc//task//root` as `/`. Relative paths remain relative. It -does not access the filesystem or resolve other symbolic links. +keeps absolute Windows paths rooted to their drive. It does not access the +filesystem or resolve other symbolic links. Action types are alternatives, not layers. A recognized shell action is a `command.exec`, not both a `tool.call` and a `command.exec`; file and network diff --git a/internal/rule/canonical_path_test.go b/internal/rule/canonical_path_test.go index 90b236c..5b2ac4e 100644 --- a/internal/rule/canonical_path_test.go +++ b/internal/rule/canonical_path_test.go @@ -80,6 +80,26 @@ func TestCanonicalPathKeepsMissingPathEmpty(t *testing.T) { } } +func TestCanonicalPathPreservesWindowsDriveRoot(t *testing.T) { + eng := mustEngine(t, Rule{ + ID: "t.windows_drive_root", + Severity: model.SeverityHigh, + Expr: `canonical_path(event.file_path) == "C:/ProgramData/ssh/administrators_authorized_keys"`, + }) + ev := model.Event{ + EventID: "e", + EventType: model.EventFileWrite, + FilePath: `C:\..\ProgramData\ssh\administrators_authorized_keys`, + } + matches, err := eng.Eval(ev) + if err != nil { + t.Fatalf("Eval: %v", err) + } + if len(matches) != 1 { + t.Fatalf("matches = %d, want 1", len(matches)) + } +} + func TestCanonicalPathRejectsNonStringEventField(t *testing.T) { eng := mustEngine(t, Rule{ ID: "t.non_string_path", diff --git a/internal/rule/engine.go b/internal/rule/engine.go index 4875522..55ad290 100644 --- a/internal/rule/engine.go +++ b/internal/rule/engine.go @@ -60,17 +60,25 @@ func canonicalPath(value string) string { if value == "" { return "" } + volume := "" + if len(value) >= 3 && ((value[0] >= 'A' && value[0] <= 'Z') || (value[0] >= 'a' && value[0] <= 'z')) && value[1] == ':' && value[2] == '/' { + volume = value[:2] + value = value[2:] + } for { - if prefix := procRootPath.FindStringIndex(value); prefix != nil { - if prefix[1] == len(value) { - return "/" + if volume == "" { + prefix := procRootPath.FindStringIndex(value) + if prefix != nil { + if prefix[1] == len(value) { + return "/" + } + value = value[prefix[1]-1:] + continue } - value = value[prefix[1]-1:] - continue } clean := path.Clean(value) if clean == value { - return clean + return volume + clean } value = clean } diff --git a/rules/internal/checked/0378d920be6102465633215dcf6be9adb8bc87fda5a3148c8f3254f21c3c1b73.pb b/rules/internal/checked/0378d920be6102465633215dcf6be9adb8bc87fda5a3148c8f3254f21c3c1b73.pb new file mode 100644 index 0000000..3303f62 --- /dev/null +++ b/rules/internal/checked/0378d920be6102465633215dcf6be9adb8bc87fda5a3148c8f3254f21c3c1b73.pb @@ -0,0 +1,870 @@ +  +event equals  +event equals   +event  equals   logical_and  +logical_or +shell_commands  +command + +redirect  in_listcanonical_path_string + +redirectmatches_string!  logical_and # +@result$  logical_not%not_strictly_false & +@result'  +logical_or ( +@result * +command,matches_string . +command2canonical_path_string 3 +arg4matches_string 7 +@result8  logical_not9not_strictly_false : +@result;  +logical_or < +@result>  logical_and?  logical_not @ +commandBmatches_string D +command H +argImatches_string L +@resultM  logical_notNnot_strictly_false O +@resultP  +logical_or Q +@resultS  +logical_orT  logical_andU  +logical_or V +commandXmatches_string[  lists_range \ +command^  list_sizea +ib greater_int64dcanonical_path_string e +commandg  +index_listh +iimatches_stringk  logical_andl +i mequals o +commandq  +index_listr +issubtract_int64umatches_stringw  +logical_ory  lists_rangez +i} +j ~equals +command  +index_list +jmatches_string  +logical_or +@resultnot_strictly_false +@result  logical_and +@result  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and  +logical_or +commandmatches_string +command  list_size greater_int64  logical_and  lists_range +command  list_size +i greater_int64canonical_path_string +command  +index_list +imatches_string  logical_and +commandmatches_string +commandmatches_string  logical_not +command + +arg in_list + +argstarts_with_string  +logical_or + +argmatches_string  +logical_or +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and +i equals +command  list_sizesubtract_int64 +command  +list_slice +i  add_int64 +command  list_size + +argmatches_string +@resultnot_strictly_false +@result  logical_and +@result  +logical_or  logical_and  +logical_or +commandmatches_string +command  +index_list +isubtract_int64matches_string  logical_not +command + +argmatches_string +@result  logical_notnot_strictly_false +@result  +logical_or +@result +i equals +command  list_sizesubtract_int64 +command  +list_slice +i  add_int64 +command  list_size + +argmatches_string +@resultnot_strictly_false +@result  logical_and +@result  +logical_or  logical_and  +logical_or  logical_and  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and  +logical_or +commandmatches_string +command  list_size greater_int64  logical_and  lists_range +command  list_size +i greater_int64canonical_path_string +command  +index_list +imatches_string  lists_range +command  list_size  +source greater_int64canonical_path_string +command  +index_list  +sourcematches_string  logical_and  +source equals  logical_not +command  +index_list  +sourcesubtract_int64matches_string  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_andcanonical_path_string +command  +index_list +imatches_string  lists_range +command  list_size  +source greater_int64canonical_path_string +command  +index_list  +sourcematches_string  logical_and  +source equals  logical_not +command  +index_list  +sourcesubtract_int64matches_string  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and  +logical_or  logical_and +command  +index_list +isubtract_int64matches_string  logical_not  lists_range +command  list_size +k greater_int64 +i +command  +index_list +k in_list +command  +index_list +kstarts_with_string  +logical_or +command  +index_list +kmatches_string  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and +i greater_int64  logical_not +command + +arg in_list + +argstarts_with_string  +logical_or + +argmatches_string  +logical_or +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and +command  +list_slice +i  add_int64 +command  list_size + +argmatches_string +@resultnot_strictly_false +@result  logical_and +@result  logical_and  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  lists_range +command  list_size  +target greater_int64 +command  +index_list  +targetstarts_with_stringcanonical_path_string +command  +index_list  +targetstring_substring_intmatches_string  lists_range +command  list_size  +source greater_int64canonical_path_string +command  +index_list  +sourcematches_string  logical_and  +source equals  logical_not +command  +index_list  +sourcesubtract_int64matches_string  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_andcanonical_path_string +command  +index_list  +targetstring_substring_intmatches_string  lists_range +command  list_size  +source greater_int64canonical_path_string +command  +index_list  +sourcematches_string  logical_and  +source equals  logical_not +command  +index_list  +sourcesubtract_int64matches_string  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and  +logical_or  logical_and +command  +index_list  +targetmatches_stringcanonical_path_string +command  +index_list  +targetstring_substring_int +command  +index_list  +targetstring_index_of_string  add_int64matches_string  lists_range +command  list_size  +source greater_int64canonical_path_string +command  +index_list  +sourcematches_string  logical_and  +source equals  logical_not +command  +index_list  +sourcesubtract_int64matches_string  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_andcanonical_path_string +command  +index_list  +targetstring_substring_int +command  +index_list  +targetstring_index_of_string  add_int64matches_string  lists_range +command  list_size  +source greater_int64canonical_path_string +command  +index_list  +sourcematches_string  logical_and  +source equals  logical_not +command  +index_list  +sourcesubtract_int64matches_string  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and  +logical_or  logical_and  +logical_or  logical_and  logical_not  lists_range +command  list_size +k greater_int64  +target +command  +index_list +k in_list +command  +index_list +kstarts_with_string  +logical_or +command  +index_list +kmatches_string  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  +logical_or  logical_and  +logical_or +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and +: + + + +: + + +  +: + + + +   2 +Jrule.ShellCommandJrule.ShellCommand2 +Jrule.ShellRedirectJrule.ShellRedirect +2 +Jrule.ShellRedirect !"#$%&'()*Jrule.ShellCommand+,-.Jrule.ShellCommand +/2 +23456789:;<=>?@Jrule.ShellCommandABCDJrule.ShellCommand +E2 +HIJKLMNOPQRSTUVJrule.ShellCommandWXY +[2 +\Jrule.ShellCommand +]2 +^abcdeJrule.ShellCommand +f2 +ghijklmnoJrule.ShellCommand +p2 +qrstuvw +y2 +z}~Jrule.ShellCommand 2 +Jrule.ShellCommandJrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommandJrule.ShellCommandJrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommandJrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommandJrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +"s2s_&&_2_||_=29_==_* + " +event +event_type2 command.exec 2_&&_622_==_* + " +event source_type +2otel? 2;_==_ +* +  " +event +event_type 2command.resultqJq +command" +shell_commands@result"*520@not_strictly_false2!_" +@result2p2p_||_" +@resultp2p_||_2_||_ U2 _||_)J +redirect* + " +command redirects@result""*2%2.@not_strictly_false$2!_ #" +@result2'2_||_ &" +@result!2_&&_D2@@in* +" + +redirectop: + 2write + 2append2 +22.canonical_path* +" + +redirecttargetmatches 2(?i)^((~|\$HOME|\$\{HOME\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\.ssh/authorized_keys2?|(\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\.ssh/authorized_keys2?|(\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$: (" +@resultT2_&&_>2_&&_K,2G ++* + *" +commandnamematches!-2(?i)^(tee|truncate|rm|sed)$=J +arg/* + ." +commandargv@result"6*292.@not_strictly_false82!_ 7" +@result2;2_||_ :" +@result42 +22canonical_path 3" +argmatches52(?i)^((~|\$HOME|\$\{HOME\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\.ssh/authorized_keys2?|(\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\.ssh/authorized_keys2?|(\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$: <" +@resultS2_||_C?2?!_9B25 +A* + @" +commandnamematchesC 2 (?i)^sed$RJ +argE* + D" +commandargv@result"K*2N2.@not_strictly_falseM2!_ L" +@result2VP2R_||_ O" +@result;I27 + H" +argmatches!J2(?i)^(-i|--in-place)(=.*)?$: Q" +@result +2 +_&&_~X2z +W* + V" +commandnamematchesTYP2N(?i)^(set-content|add-content|clear-content|out-file|remove-item|ri|new-item)$J +i8[24 lists.range%^2! +]* + \" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_k2_&&_b2_>_a" +ici2 +Dd2@canonical_path.g2*_[_]f* + e" +commandargvh" +imatchesj2(?i)^((~|\$HOME|\$\{HOME\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\.ssh/authorized_keys2?|(\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\.ssh/authorized_keys2?|(\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$2_||_w2_||_m2_==_l" +inzu2v +Aq2=_[_]p* + o" +commandargvs2_-_r" +itmatches(v$2"(?i)^-(path|literalpath|filepath)$J +jy2 lists.rangez" +i@result"**2%@not_strictly_false" +@result22_&&_" +@result2_||_~2_==_}" +j2 +22-_[_]* +" +commandargv" +jmatchesVQ2O(?i)^-(force|nonewline|passthru|append|noclobber|recurse|whatif|confirm)(:.*)?$:" +@result:" +@resultZ2Z_||_2_&&_2_&&_]2X +* +" +commandnamematches0+2)(?i)^(cp|mv|install|copy-item|move-item)$=28_>_(2# +* +" +commandargvsizeJ +i<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_2_&&_2_>_" +i2 +I2Dcanonical_path22-_[_]* +" +commandargv" +imatches2(?i)^((~|\$HOME|\$\{HOME\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\.ssh/authorized_keys2?|(\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\.ssh/authorized_keys2?|(\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$2_||_2_||_H2C +* +" +commandnamematches2(?i)^(mv|move-item)$2_&&_2_&&_F2A +* +" +commandnamematches2(?i)^(cp|install)$2!_J +arg* +" +commandargv@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_||_2_||_C2>@in +" +arg+:& + 2-t +2--target-directory924 + +" +arg +startsWith2--target-directory=.2) + +" +argmatches 2 ^-[^-]*t.+$:" +@result2_||_T2O_==_" +i=28_-_(2# +* +" +commandargvsizeJ +argr2m +* +" +commandargvslice2_+_" +i(2# +* +" +commandargvsize@result"**2%@not_strictly_false" +@result22_&&_" +@result|2w + +" +argmatches`[2Y(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$:" +@result2_&&_C2> +* +" +commandnamematches2(?i)^copy-item$2_||_r2m +G2B_[_]* +" +commandargv2_-_" +imatches2(?i)^-destination$2_&&_2!_J +arg* +" +commandargv@result"*520@not_strictly_false2!_" +@result2R2M_||_" +@result520 + +" +argmatches2(?i)^-destination$:" +@result2_||_T2O_==_" +i=28_-_(2# +* +" +commandargvsizeJ +argr2m +* +" +commandargvslice2_+_" +i(2# +* +" +commandargvsize@result"**2%@not_strictly_false" +@result2~2y_&&_" +@resulta2\ + +" +argmatchesE@2>(?i)^-(force|recurse|container|passthru|whatif|confirm)(:.*)?$:" +@result:" +@resultD2D_&&_2_&&_I2D +* +" +commandnamematches2(?i)^(cp|mv|install)$=28_>_(2# +* +" +commandargvsizeB2B_||_J +i<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_2_&&_2_>_" +i 2 _||_2_&&_2 +I2Dcanonical_path22-_[_]* +" +commandargv" +imatches2(?i)^((~|\$HOME|\$\{HOME\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))|(\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+))/\.ssh$J +source<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_2_&&_"2_>_ " +source2} +N2Icanonical_path722_[_]* +" +commandargv " +sourcematches"2(?i)(^|/)authorized_keys2?$2_||_#2_==_ " +source2!_2 +L2G_[_]* +" +commandargv"2_-_ " +sourcematches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" +@result2_&&_2 +I2Dcanonical_path22-_[_]* +" +commandargv" +imatchesE@2>(?i)^(\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh$J +source<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_2_&&_"2_>_ " +source2 +N2Icanonical_path722_[_]* +" +commandargv " +sourcematches/*2((?i)(^|/)administrators_authorized_keys$2_||_#2_==_ " +source2!_2 +L2G_[_]* +" +commandargv"2_-_ " +sourcematches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" +@result 2 _||_2_&&_}2x +G2B_[_]* +" +commandargv2_-_" +imatches$2(?i)^(-t|--target-directory)$2!_J +k<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_2_>_" +k" +i2_||_2_||_k2f@in22-_[_]* +" +commandargv" +k+:& + 2-t +2--target-directorya2\ +22-_[_]* +" +commandargv" +k +startsWith2--target-directory=V2Q +22-_[_]* +" +commandargv" +kmatches 2 ^-[^-]*t.+$:" +@result2_&&_2_&&_2_>_" +i2!_J +arg* +" +commandargv@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_||_2_||_C2>@in +" +arg+:& + 2-t +2--target-directory924 + +" +arg +startsWith2--target-directory=.2) + +" +argmatches 2 ^-[^-]*t.+$:" +@resultJ +argr2m +* +" +commandargvslice2_+_" +i(2# +* +" +commandargvsize@result"**2%@not_strictly_false" +@result22_&&_" +@result|2w + +" +argmatches`[2Y(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$:" +@result:" +@result&J& +target<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result2%2%_||_" +@result%2%_&&_ 2 _&&_"2_>_ " +target 2 _||_2_&&_f2a +722_[_]* +" +commandargv " +target +startsWith2--target-directory=2_||_2_&&_2 +i2dcanonical_pathR2M +722_[_]* +" +commandargv " +target substringmatches2(?i)^((~|\$HOME|\$\{HOME\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))|(\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+))/\.ssh$J +source<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_2_&&_"2_>_ " +source2} +N2Icanonical_path722_[_]* +" +commandargv " +sourcematches"2(?i)(^|/)authorized_keys2?$2_||_#2_==_ " +source2!_2 +L2G_[_]* +" +commandargv"2_-_ " +sourcematches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" +@result2_&&_2 +i2dcanonical_pathR2M +722_[_]* +" +commandargv " +target substringmatchesE@2>(?i)^(\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh$J +source<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_2_&&_"2_>_ " +source2 +N2Icanonical_path722_[_]* +" +commandargv " +sourcematches/*2((?i)(^|/)administrators_authorized_keys$2_||_#2_==_ " +source2!_2 +L2G_[_]* +" +commandargv"2_-_ " +sourcematches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" +@result2_&&_[2V +722_[_]* +" +commandargv " +targetmatches 2 ^-[^-]*t.+$2_||_2_&&_2 +2canonical_path2 +722_[_]* +" +commandargv " +target substringf2a_+_Q2L +722_[_]* +" +commandargv " +targetindexOf2tmatches2(?i)^((~|\$HOME|\$\{HOME\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))|(\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+))/\.ssh$J +source<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_2_&&_"2_>_ " +source2} +N2Icanonical_path722_[_]* +" +commandargv " +sourcematches"2(?i)(^|/)authorized_keys2?$2_||_#2_==_ " +source2!_2 +L2G_[_]* +" +commandargv"2_-_ " +sourcematches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" +@result2_&&_2 +2canonical_path2 +722_[_]* +" +commandargv " +target substringf2a_+_Q2L +722_[_]* +" +commandargv " +targetindexOf2tmatchesE@2>(?i)^(\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh$J +source<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_2_&&_"2_>_ " +source2 +N2Icanonical_path722_[_]* +" +commandargv " +sourcematches/*2((?i)(^|/)administrators_authorized_keys$2_||_#2_==_ " +source2!_2 +L2G_[_]* +" +commandargv"2_-_ " +sourcematches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" +@result2!_J +k<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_#2_>_" +k " +target2_||_2_||_k2f@in22-_[_]* +" +commandargv" +k+:& + 2-t +2--target-directorya2\ +22-_[_]* +" +commandargv" +k +startsWith2--target-directory=V2Q +22-_[_]* +" +commandargv" +kmatches 2 ^-[^-]*t.+$:" +@result:" +@result:" +@result*4Ynumbat:cel-env-v1:sha256:0378d920be6102465633215dcf6be9adb8bc87fda5a3148c8f3254f21c3c1b73q + +  !""""#$$%&&&&&&''((()))*****++++,------....///11224444566788999999:<<==????@AABCCDDDDDEEEFFFFFGGGG"""""("-":"=" G" +L" X" [" D"$"q""""""""""""" "!"""#"$"%"&"'"(")"*"+","-"."/"2"3"4"5"6"7"8"9":";"<"=">"?"@"A"B"C"D"E"H"I"J"K"L"M"N"O"P"Q"R"S"T"U"V"W"X"Y"[ "\ "] "^ "a +"b +"c +"d +"e +"f +"g +"h +"i +"j +"k +"l "m "n "o "p "q "r "s "t "u "v "w "y "z "} "~ " """"""" " " " " " " " " " " " " " " " " " " """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " " " " " " " " " " " " "!" "!"!"!"!"!"!"!"!"!"!"!"!"!"!" " " " " " " " ""#"#"#"#"#"#"#"#"#"#"$"$"$"$"$"$"$"$"$"$"$"$"%"%"%"%"%"%"%"%"%"%"%"%"%"%"$"$"$"$"$"$"$"$"#""""'"'"'"'"'"'"'"'"'"'"'"'"'"("("("("("("("("("("("("(")")")")"(")")")")")")")"("'"'"'"'"'"'"'"'"'"*"*"*"*"*"*"*"*"*"*"*"+"+"+"+"+"+"+"+"*"*"*"*"*"*"*"*"*",",",",",",",",",",",",",",",",",",","+"*"&"""""""""."."."."."."."."."."."/"/"/"/"/"/"/"0"0"0"0"1"1"1"1"2"2"2"2"2"2"2"2"2"2"2"3"3"3"3"3"3"3"3"3"3"3"3"3"2"1"1"1"1"1"1"1"1"1"4"4"4"4"5"5"5"5"5"6"6"6"6"6"6"6"6"6"6"6"6"7"7"6"7"7"7"7"7"7"7"7"7"7"7"7"7"7"6"6"6"6"6"6"6"6"5"4"/"9"9"9"9"9"9":":":":":":":":":":":":":":":":"<"<"<"<"<"<"<"="="="="="="="<"="=">">">">">">">">">">">"="<"<"<"<"<"<"<"<"<"?"?"?"?"?"?"?"@"@"@"@"@"@"@"@"@"A"A"A"A"A"A"A"A"A"B"B"B"B"B"A"B"B"B"B"B"B"B"B"C"C"C"C"B"B"A"A"A"A"A"A"A"A"@"?"9"9"."D"D"D"D"D"D"E"E"E"E"E"E"E"E"E"E"E"E"E"E"F"F"E"F"F"F"F"F"F"F"E"D"D"D"D"D"D"D"D"D"."."."."."."."."-"""""""""""n \ No newline at end of file diff --git a/rules/internal/checked/7f5aef9eb4a0b5fe04a85b8dcad0f093f09d1c7fe04c77e58f5e115d356f9ccb.pb b/rules/internal/checked/7f5aef9eb4a0b5fe04a85b8dcad0f093f09d1c7fe04c77e58f5e115d356f9ccb.pb new file mode 100644 index 0000000..94a120d --- /dev/null +++ b/rules/internal/checked/7f5aef9eb4a0b5fe04a85b8dcad0f093f09d1c7fe04c77e58f5e115d356f9ccb.pb @@ -0,0 +1,899 @@ +  +event equals  +event equals   +logical_or +canonical_path_string   +event matches_string  logical_and  +event equals  +event equals  +event equals  logical_and  +logical_or +shell_commands ! +command#matches_string%  logical_not & +command * +arg+matches_string - +arg.matches_string0  +logical_or 1 +arg2matches_string4  +logical_or 5 +arg 6 in_list:  +logical_or < +@result=  logical_not>not_strictly_false ? +@result@  +logical_or A +@resultC  logical_andD  logical_not E +command I +argJmatches_string L +argMmatches_stringO  +logical_or Q +@resultR  logical_notSnot_strictly_false T +@resultU  +logical_or V +@result X +commandZ  +list_slice \ +command^  list_size a +argbmatches_string d +arg e in_listi  +logical_or k +@resultlnot_strictly_false m +@resultn  logical_and o +@resultq  logical_ands  lists_range t +commandv  list_sizey +iz greater_int64 | +command~  +index_list +istarts_with_stringcanonical_path_string +command  +index_list +istring_substring_intmatches_string  logical_and +command  +index_list +imatches_stringcanonical_path_string +command  +index_list +istring_substring_int +command  +index_list +istring_index_of_string  add_int64matches_string  logical_and  +logical_or +command  +index_list +i equals +command  +index_list +imatches_string  +logical_or +i  add_int64  +less_int64 +command  list_size  logical_andcanonical_path_string +command  +index_list +i  add_int64matches_string  logical_and  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  +logical_or  logical_and +command + +redirect in_listcanonical_path_string + +redirectmatches_string  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  +logical_or  lists_range +command  list_size +i greater_int64canonical_path_string +command  +index_list +imatches_string  logical_and +commandmatches_string +commandmatches_string +command + +argmatches_string +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and  +logical_or +commandmatches_string  +logical_or +commandmatches_string  logical_not +command + +arg in_list + +argstarts_with_string  +logical_or + +argmatches_string  +logical_or +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and +i equals +command  list_sizesubtract_int64 +command  +index_list +isubtract_int64matches_string  +logical_or +command  +list_slice +i  add_int64 +command  list_size + +argmatches_string +@resultnot_strictly_false +@result  logical_and +@result  +logical_or  logical_and  +logical_or +commandmatches_string +i equals +command  +index_list +isubtract_int64matches_string  +logical_or  lists_range +i +j equals +command  +index_list +jmatches_string  +logical_or +@resultnot_strictly_false +@result  logical_and +@result  +logical_or  logical_and  +logical_or +commandmatches_string +command  +index_list +isubtract_int64matches_string +i greater_int64  lists_range +command  list_size +jless_equals_int64 +i +command  +index_list +jmatches_string  +logical_or +@resultnot_strictly_false +@result  logical_and +@result  logical_and  +logical_or  logical_and  +logical_or +commandmatches_string +i equals +command  +index_list +isubtract_int64matches_string  +logical_or +i equals +command  list_sizesubtract_int64  +logical_or  lists_range +i +j equals +command  +index_list +jmatches_string  +logical_or +@resultnot_strictly_false +@result  logical_and +@result  +logical_or  logical_and  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  +logical_or +commandmatches_string  lists_range +command  list_size +i greater_int64canonical_path_string +command  +index_list +i equals  logical_and +command  +index_list +isubtract_int64matches_string  logical_not  lists_range +command  list_size +k greater_int64 +i +command  +index_list +k in_list +command  +index_list +kstarts_with_string  +logical_or +command  +index_list +kmatches_string  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and  lists_range +command  list_size +j greater_int64 +j  +not_equals +i  logical_and  logical_not +command  +index_list +jstarts_with_string  logical_and +j equals  logical_not +command  +index_list +jsubtract_int64matches_string  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and +i greater_int64  logical_not +command + +arg in_list + +argstarts_with_string  +logical_or + +argmatches_string  +logical_or +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and +command  +list_slice +i  add_int64 +command  list_size + +argmatches_string +@resultnot_strictly_false +@result  logical_and +@result  logical_and  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and  lists_range +command  list_size  +target greater_int64 +command  +index_list  +targetstarts_with_stringcanonical_path_string +command  +index_list  +targetstring_substring_int equals  logical_and +command  +index_list  +targetmatches_stringcanonical_path_string +command  +index_list  +targetstring_substring_int +command  +index_list  +targetstring_index_of_string  add_int64 equals  logical_and  +logical_or  logical_and  logical_not  lists_range +command  list_size +k greater_int64  +target +command  +index_list +k in_list +command  +index_list +kstarts_with_string  +logical_or +command  +index_list +kmatches_string  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  lists_range +command  list_size +i greater_int64  logical_not +command  +index_list +istarts_with_string  logical_and +i equals  logical_not +command  +index_list +isubtract_int64matches_string  +logical_or  logical_and +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and  +logical_or  +logical_or +@result  logical_notnot_strictly_false +@result  +logical_or +@result  logical_and  +logical_or +: + + + +: + + +  +  +: + +  +  +: + + + +: + + + +: + + +2 +Jrule.ShellCommand!Jrule.ShellCommand"#$%&Jrule.ShellCommand +'2 +*+,-./0123456 +72 +89:;<=>?@ABCDEJrule.ShellCommand +F2 +IJKLMNOPQRSTUVWXJrule.ShellCommand +Y2 + +Z2 +[\Jrule.ShellCommand +]2 +^abcde +f2 +ghijklmnopq +s2 +tJrule.ShellCommand +u2 +vyz{|Jrule.ShellCommand +}2 +~Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand2 +Jrule.ShellRedirectJrule.ShellRedirect 2 +Jrule.ShellRedirect 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommandJrule.ShellCommandJrule.ShellCommand 2 +Jrule.ShellCommandJrule.ShellCommandJrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommandJrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommandJrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommandJrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 + 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +Jrule.ShellCommand 2 +"`2`_||_2_&&_ 2_||_;27_==_* + " +event +event_type 2 +file.write<28_==_* + " +event +event_type 2 file.deleteo 2k +2 +2.canonical_path * +  " +event file_pathmatches,(2&^/etc/sudoers$|^/etc/sudoers\.d/[^/]+$^2^_&&_2_||_=29_==_* + " +event +event_type2 command.exec2_&&_622_==_* + " +event source_type +2otel?2;_==_* + " +event +event_type2command.result\J\ +command" +shell_commands@result"*520@not_strictly_false2!_" +@result2[2[_||_" +@result[2[_||_2_||_2_&&_C2_&&_<#28 +"* + !" +commandnamematches$2 (?i)^visudo$%2!_BJ +arg'* + &" +commandargv@result";*2>2.@not_strictly_false=2!_ <" +@result2@2_||_ ?" +@result42_||_02_||_K+2G + *" +argmatches1,-2+^(--check|--help|--version|--export)(=.*)?$4.20 + -" +argmatches/2^-[qs]*c[qs]*(f.*)?$g:2c_||_+22' + 1" +argmatches3 2 ^-[qs]*x.*$.62*@in 5" +arg7: +82-h +92-V: A" +@result2_||_q2_&&_D2!_WJ +argF* + E" +commandargv@result"P*2S2.@not_strictly_falseR2!_ Q" +@result2U2~_||_ T" +@resultgO2c_||_.J2* + I" +argmatchesK2^--file(=.*)?$+M2' + L" +argmatchesN 2 ^-[qs]*f.*$: V" +@resultpJ +argUZ2Q +Y* + X" +commandargvslice[%^2! +]* + \" +commandargvsize@result"j*(l2$@not_strictly_false k" +@result2n2_&&_ m" +@resultoi2k_||_(b2$ + a" +argmatchesc +2^-[qs]+$9e25@in d" +arg#f: + g 2--quiet +h +2--strict: o" +@result +J + +i8s24 lists.range%v2! +u* + t" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_z2_>_y" +i{2_||_2_||_2_&&_Q2L +.~2*_[_]}* + |" +commandargv" +i +startsWith 2--file=2 +d2_canonical_pathM2H +22-_[_]* +" +commandargv" +i substringmatches!2^/etc/sudoers(\.d/[^/]+)?$2_&&_V2Q +22-_[_]* +" +commandargv" +imatches 2 ^-[qs]*f.+$2 +2canonical_path2 +22-_[_]* +" +commandargv" +i substringa2\_+_L2G +22-_[_]* +" +commandargv" +iindexOf2fmatches!2^/etc/sudoers(\.d/[^/]+)?$2_&&_2_&&_2_||_N2I_==_22-_[_]* +" +commandargv" +i 2--fileT2O +22-_[_]* +" +commandargv" +imatches 2 ^-[qs]*f$S2N_<_2_+_" +i(2# +* +" +commandargvsize2 +^2Ycanonical_pathG2B_[_]* +" +commandargv2_+_" +imatches!2^/etc/sudoers(\.d/[^/]+)?$:" +@resultJ +redirect * +" +command redirects@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_J2E@in* +" + +redirectop": + 2write + 2appendl2g +520canonical_path* +" + +redirecttargetmatches% 2(?i)^/etc/sudoers(\.d/[^/]+)?$:" +@resultE2D_||_J +i<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_2_&&_2_>_" +i2{ +I2Dcanonical_path22-_[_]* +" +commandargv" +imatches% 2(?i)^/etc/sudoers(\.d/[^/]+)?$2_||_ 2 _||_2_||_K2F +* +" +commandnamematches2(?i)^(rm|truncate|tee)$2_&&_=28 +* +" +commandnamematches 2 (?i)^sed$J +arg* +" +commandargv@result"*520@not_strictly_false2!_" +@result2[2V_||_" +@result>29 + +" +argmatches"2(?i)^(-i|--in-place)(=.*)?$:" +@result 2 _||_<27 +* +" +commandnamematches +2(?i)^mv$2_&&_2_&&_F2A +* +" +commandnamematches2(?i)^(cp|install)$2!_J +arg* +" +commandargv@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_||_2_||_C2>@in +" +arg+:& + 2-t +2--target-directory924 + +" +arg +startsWith2--target-directory=.2) + +" +argmatches 2 ^-[^-]*t.+$:" +@result2_||_2_||_T2O_==_" +i=28_-_(2# +* +" +commandargvsize}2x +G2B_[_]* +" +commandargv2_-_" +imatches$2(?i)^(-t|--target-directory)$J +argr2m +* +" +commandargvslice2_+_" +i(2# +* +" +commandargvsize@result"**2%@not_strictly_false" +@result22_&&_" +@result|2w + +" +argmatches`[2Y(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$:" +@result2_||_ +2 +_||_2_&&_2} +* +" +commandnamematchesUP2N(?i)^(set-content|add-content|clear-content|out-file|remove-item|ri|new-item)$2_||_2_||_2_==_" +i2} +G2B_[_]* +" +commandargv2_-_" +imatches)$2"(?i)^-(path|literalpath|filepath)$J +j2 lists.range" +i@result"**2%@not_strictly_false" +@result22_&&_" +@result2_||_2_==_" +j2 +22-_[_]* +" +commandargv" +jmatchesVQ2O(?i)^-(force|nonewline|passthru|append|noclobber|recurse|whatif|confirm)(:.*)?$:" +@result2_&&_C2> +* +" +commandnamematches2(?i)^copy-item$2_||_r2m +G2B_[_]* +" +commandargv2_-_" +imatches2(?i)^-destination$2_&&_2_>_" +iJ +j<27 lists.range(2# +* +" +commandargvsize@result"**2%@not_strictly_false" +@result22_&&_" +@result2_||_2_<=_" +j" +i2 +22-_[_]* +" +commandargv" +jmatchesE@2>(?i)^-(force|recurse|container|passthru|whatif|confirm)(:.*)?$:" +@result2_&&_C2> +* +" +commandnamematches2(?i)^move-item$2_||_2_||_2_==_" +i2 +G2B_[_]* +" +commandargv2_-_" +imatches,'2%(?i)^-(path|literalpath|destination)$2_||_T2O_==_" +i=28_-_(2# +* +" +commandargvsizeJ +j2 lists.range" +i@result"**2%@not_strictly_false" +@result22_&&_" +@result2_||_2_==_" +j2 +22-_[_]* +" +commandargv" +jmatchesE@2>(?i)^-(force|recurse|container|passthru|whatif|confirm)(:.*)?$:" +@result:" +@result%2%_||_2_&&_I2D +* +" +commandnamematches2(?i)^(cp|mv|install)$J +i<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_2_&&_2_>_" +im2h_==_I2Dcanonical_path22-_[_]* +" +commandargv" +i2/etc/sudoers.d2_||_ +2 +_&&_2_&&_}2x +G2B_[_]* +" +commandargv2_-_" +imatches$2(?i)^(-t|--target-directory)$2!_J +k<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_2_>_" +k" +i2_||_2_||_k2f@in22-_[_]* +" +commandargv" +k+:& + 2-t +2--target-directorya2\ +22-_[_]* +" +commandargv" +k +startsWith2--target-directory=V2Q +22-_[_]* +" +commandargv" +kmatches 2 ^-[^-]*t.+$:" +@resultJ +j<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_K2F_&&_2_>_" +j2_!=_" +j" +i2_&&_Z2U!_O2J +22-_[_]* +" +commandargv" +j +startsWith2-2_||_2_==_" +j2!_2 +G2B_[_]* +" +commandargv2_-_" +jmatches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" +@result2_&&_2_&&_2_>_" +i2!_J +arg* +" +commandargv@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_||_2_||_C2>@in +" +arg+:& + 2-t +2--target-directory924 + +" +arg +startsWith2--target-directory=.2) + +" +argmatches 2 ^-[^-]*t.+$:" +@resultJ +argr2m +* +" +commandargvslice2_+_" +i(2# +* +" +commandargvsize@result"**2%@not_strictly_false" +@result22_&&_" +@result|2w + +" +argmatches`[2Y(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$:" +@result:" +@result2_&&_ J +target<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result2 +2 +_||_" +@result 2 _&&_2_&&_"2_>_ " +target2_||_2_&&_f2a +722_[_]* +" +commandargv " +target +startsWith2--target-directory=2_==_i2dcanonical_pathR2M +722_[_]* +" +commandargv " +target substring2/etc/sudoers.d2_&&_[2V +722_[_]* +" +commandargv " +targetmatches 2 ^-[^-]*t.+$2_==_2canonical_path2 +722_[_]* +" +commandargv " +target substringf2a_+_Q2L +722_[_]* +" +commandargv " +targetindexOf2t2/etc/sudoers.d2!_J +k<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_#2_>_" +k " +target2_||_2_||_k2f@in22-_[_]* +" +commandargv" +k+:& + 2-t +2--target-directorya2\ +22-_[_]* +" +commandargv" +k +startsWith2--target-directory=V2Q +22-_[_]* +" +commandargv" +kmatches 2 ^-[^-]*t.+$:" +@result:" +@resultJ +i<27 lists.range(2# +* +" +commandargvsize@result"*520@not_strictly_false2!_" +@result22_||_" +@result2_&&_2_&&_2_>_" +iZ2U!_O2J +22-_[_]* +" +commandargv" +i +startsWith2-2_||_2_==_" +i2!_2 +G2B_[_]* +" +commandargv2_-_" +imatches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" +@result:" +@result*2Ynumbat:cel-env-v1:sha256:7f5aef9eb4a0b5fe04a85b8dcad0f093f09d1c7fe04c77e58f5e115d356f9ccbO + +  !!!"""####$$%%%&&&&'((((())))*++++++,,,,--...///000111222223444444"" +""")".":"=" &" +_" `" e" x"y"L""""""""""""""""!"""#"$"%"&"'"*"+","-"."/"0"1"2"3"4"5"6"7"8"9":";"<"=">"?"@"A"B"C"D"E"F"I"J"K"L"M"N"O"P"Q"R"S"T"U"V"W"X"Y"Z"["\"]"^"a"b"c"d"e"f"g"h"i"j"k"l"m"n"o"p"q"s"t"u"v"y"z"{"|"}"~"""""""""""""" " " " " " " +" +" +" +" +" +" +" +" +" +" +" +" +" +" +" +" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " +"""""""""""" " " " " " " " """""" " " " " " " " " " """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " "!"!"!"!"!"!"!"!"!"""""""""""""""!"""""""""""""""""#"#"#"#"#"#"#"#"#"$"$"$"$"$"$"$"$"$"$"$"$"$"$"%"%"%"%"%"%"$"#"#"#"#"#"#"#"#"#"#"%"%"%"%"&"&"&"&"&"&"&"&"&"&"&"&"&"&"&"&"&"&"&"&"'"'"'"'"'"'"'"&"&"%"%"%"%"%"%"%"%"%"("("("("("("("("("("(")")")")")")")")"("("("("("("("("("*"*"*"*"*"*"*"*"*"*"*"*"*"*"*"*"*"*"*")"("""!"!"!"!"!"!"!"!"!",",",",",",",",",",","-"-"-"-"-"-"-"-"-"-"-"-"."."."."."."."."."."."/"/"/"/"/"/"/"/"/"/"/".".","/"/"/"0"0"0"0"0"0"0"0"0"0"0"0"0"1"1"1"1"1"1"1"1"1"1"1"1"1"1"0"0"0"0"0"0"0"0"0"/",",",",",",",","2"2"2"2"2"2"2"2"2"2"3"3"3"3"2"3"3"3"3"3"3"3"3"3"3"3"3"3"3"2"2"2"2"2"2"2"2"2"+" """""""""" \ No newline at end of file diff --git a/rules/internal/checked/b20be62ab63e4da2e7ce3d6a9f128d1107084d3377e2f43c199139152fc28204.pb b/rules/internal/checked/b20be62ab63e4da2e7ce3d6a9f128d1107084d3377e2f43c199139152fc28204.pb deleted file mode 100644 index 8261059bc132a3e6e9660795dd13da1a9d7a96d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18690 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 PL&c9Q7UDlSK;-`bv+-6Y diff --git a/rules/persistence/ssh_authorized_keys_command.yaml b/rules/persistence/ssh_authorized_keys_command.yaml index 67fb7a6..a72fbab 100644 --- a/rules/persistence/ssh_authorized_keys_command.yaml +++ b/rules/persistence/ssh_authorized_keys_command.yaml @@ -1,5 +1,5 @@ id: persistence.ssh_authorized_keys_command -version: "1.7" +version: "1.8" enabled: true title: Command targeted SSH authorized_keys description: |- @@ -42,10 +42,149 @@ expr: |- command.name.matches("(?i)^(cp|mv|install|copy-item|move-item)$") && command.argv.size() > 2 && ( - canonical_path(command.argv[command.argv.size() - 1]).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\\.ssh/authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\\.ssh/authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$") || - lists.range(command.argv.size() - 1).exists(i, - command.argv[i].matches("(?i)^-destination$") && - canonical_path(command.argv[i + 1]).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\\.ssh/authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\\.ssh/authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$") + lists.range(command.argv.size()).exists(i, + i > 0 && + canonical_path(command.argv[i]).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\\.ssh/authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\\.ssh/authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$") && + ( + command.name.matches("(?i)^(mv|move-item)$") || + ( + command.name.matches("(?i)^(cp|install)$") && + !command.argv.exists(arg, + arg in ["-t", "--target-directory"] || + arg.startsWith("--target-directory=") || + arg.matches("^-[^-]*t.+$") + ) && + ( + i == command.argv.size() - 1 || + command.argv.slice(i + 1, command.argv.size()).all(arg, + arg.matches("(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$") + ) + ) + ) || + ( + command.name.matches("(?i)^copy-item$") && + ( + command.argv[i - 1].matches("(?i)^-destination$") || + ( + !command.argv.exists(arg, arg.matches("(?i)^-destination$")) && + ( + i == command.argv.size() - 1 || + command.argv.slice(i + 1, command.argv.size()).all(arg, + arg.matches("(?i)^-(force|recurse|container|passthru|whatif|confirm)(:.*)?$") + ) + ) + ) + ) + ) + ) + ) + ) + ) || + ( + command.name.matches("(?i)^(cp|mv|install)$") && + command.argv.size() > 2 && + ( + lists.range(command.argv.size()).exists(i, + i > 0 && + ( + ( + canonical_path(command.argv[i]).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+))/\\.ssh$") && + lists.range(command.argv.size()).exists(source, + source > 0 && + canonical_path(command.argv[source]).matches("(?i)(^|/)authorized_keys2?$") && + (source == 1 || !command.argv[source - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) + ) + ) || + ( + canonical_path(command.argv[i]).matches("(?i)^(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh$") && + lists.range(command.argv.size()).exists(source, + source > 0 && + canonical_path(command.argv[source]).matches("(?i)(^|/)administrators_authorized_keys$") && + (source == 1 || !command.argv[source - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) + ) + ) + ) && + ( + ( + command.argv[i - 1].matches("(?i)^(-t|--target-directory)$") && + !lists.range(command.argv.size()).exists(k, + k > i && + ( + command.argv[k] in ["-t", "--target-directory"] || + command.argv[k].startsWith("--target-directory=") || + command.argv[k].matches("^-[^-]*t.+$") + ) + ) + ) || + ( + i > 1 && + !command.argv.exists(arg, + arg in ["-t", "--target-directory"] || + arg.startsWith("--target-directory=") || + arg.matches("^-[^-]*t.+$") + ) && + command.argv.slice(i + 1, command.argv.size()).all(arg, + arg.matches("(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$") + ) + ) + ) + ) || + ( + lists.range(command.argv.size()).exists(target, + target > 0 && + ( + ( + command.argv[target].startsWith("--target-directory=") && + ( + ( + canonical_path(command.argv[target].substring(19)).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+))/\\.ssh$") && + lists.range(command.argv.size()).exists(source, + source > 0 && + canonical_path(command.argv[source]).matches("(?i)(^|/)authorized_keys2?$") && + (source == 1 || !command.argv[source - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) + ) + ) || + ( + canonical_path(command.argv[target].substring(19)).matches("(?i)^(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh$") && + lists.range(command.argv.size()).exists(source, + source > 0 && + canonical_path(command.argv[source]).matches("(?i)(^|/)administrators_authorized_keys$") && + (source == 1 || !command.argv[source - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) + ) + ) + ) + ) || + ( + command.argv[target].matches("^-[^-]*t.+$") && + ( + ( + canonical_path(command.argv[target].substring(command.argv[target].indexOf("t") + 1)).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+))/\\.ssh$") && + lists.range(command.argv.size()).exists(source, + source > 0 && + canonical_path(command.argv[source]).matches("(?i)(^|/)authorized_keys2?$") && + (source == 1 || !command.argv[source - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) + ) + ) || + ( + canonical_path(command.argv[target].substring(command.argv[target].indexOf("t") + 1)).matches("(?i)^(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh$") && + lists.range(command.argv.size()).exists(source, + source > 0 && + canonical_path(command.argv[source]).matches("(?i)(^|/)administrators_authorized_keys$") && + (source == 1 || !command.argv[source - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) + ) + ) + ) + ) + ) && + !lists.range(command.argv.size()).exists(k, + k > target && + ( + command.argv[k] in ["-t", "--target-directory"] || + command.argv[k].startsWith("--target-directory=") || + command.argv[k].matches("^-[^-]*t.+$") + ) + ) + ) ) ) ) diff --git a/rules/privilege/sudoers_tamper.yaml b/rules/privilege/sudoers_tamper.yaml index e29792e..dd2062d 100644 --- a/rules/privilege/sudoers_tamper.yaml +++ b/rules/privilege/sudoers_tamper.yaml @@ -1,5 +1,5 @@ id: privilege.sudoers_tamper -version: "1.4" +version: "1.5" enabled: true title: Agent targeted sudoers policy for modification description: |- @@ -17,30 +17,37 @@ expr: |- ( command.name.matches("(?i)^visudo$") && !command.argv.exists(arg, - arg.matches("(?i)^(-c|--check|--help|-h|--version|-V|--export)(=.*)?$") + arg.matches("^(--check|--help|--version|--export)(=.*)?$") || + arg.matches("^-[qs]*c[qs]*(f.*)?$") || + arg.matches("^-[qs]*x.*$") || + arg in ["-h", "-V"] ) && ( ( - !command.argv.exists(arg, arg.matches("(?i)^(-f|--file)(=.*)?$")) && + !command.argv.exists(arg, + arg.matches("^--file(=.*)?$") || + arg.matches("^-[qs]*f.*$") + ) && command.argv.slice(1, command.argv.size()).all(arg, - arg.matches("(?i)^(-q|-s|--quiet|--strict)$") + arg.matches("^-[qs]+$") || + arg in ["--quiet", "--strict"] ) ) || lists.range(command.argv.size()).exists(i, i > 0 && ( - ( - command.argv[i].startsWith("-f=") && - canonical_path(command.argv[i].substring(3)).matches("^/etc/sudoers(\\.d/[^/]+)?$") - ) || ( command.argv[i].startsWith("--file=") && canonical_path(command.argv[i].substring(7)).matches("^/etc/sudoers(\\.d/[^/]+)?$") ) || ( - command.argv[i].matches("(?i)^(-f|--file)$") && + command.argv[i].matches("^-[qs]*f.+$") && + canonical_path(command.argv[i].substring(command.argv[i].indexOf("f") + 1)).matches("^/etc/sudoers(\\.d/[^/]+)?$") + ) || + ( + (command.argv[i] == "--file" || command.argv[i].matches("^-[qs]*f$")) && i + 1 < command.argv.size() && - canonical_path(command.argv[i + 1]).matches("(?i)^/etc/sudoers(\\.d/[^/]+)?$") + canonical_path(command.argv[i + 1]).matches("^/etc/sudoers(\\.d/[^/]+)?$") ) ) ) @@ -62,9 +69,17 @@ expr: |- command.name.matches("(?i)^mv$") || ( command.name.matches("(?i)^(cp|install)$") && + !command.argv.exists(arg, + arg in ["-t", "--target-directory"] || + arg.startsWith("--target-directory=") || + arg.matches("^-[^-]*t.+$") + ) && ( i == command.argv.size() - 1 || - command.argv[i - 1].matches("(?i)^(-t|--target-directory)$") + command.argv[i - 1].matches("(?i)^(-t|--target-directory)$") || + command.argv.slice(i + 1, command.argv.size()).all(arg, + arg.matches("(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$") + ) ) ) || ( @@ -104,6 +119,71 @@ expr: |- ) ) ) + ) || + ( + command.name.matches("(?i)^(cp|mv|install)$") && + lists.range(command.argv.size()).exists(i, + i > 0 && + canonical_path(command.argv[i]) == "/etc/sudoers.d" && + ( + ( + command.argv[i - 1].matches("(?i)^(-t|--target-directory)$") && + !lists.range(command.argv.size()).exists(k, + k > i && + ( + command.argv[k] in ["-t", "--target-directory"] || + command.argv[k].startsWith("--target-directory=") || + command.argv[k].matches("^-[^-]*t.+$") + ) + ) && + lists.range(command.argv.size()).exists(j, + j > 0 && + j != i && + !command.argv[j].startsWith("-") && + (j == 1 || !command.argv[j - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) + ) + ) || + ( + i > 1 && + !command.argv.exists(arg, + arg in ["-t", "--target-directory"] || + arg.startsWith("--target-directory=") || + arg.matches("^-[^-]*t.+$") + ) && + command.argv.slice(i + 1, command.argv.size()).all(arg, + arg.matches("(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$") + ) + ) + ) + ) || + ( + lists.range(command.argv.size()).exists(target, + target > 0 && + ( + ( + command.argv[target].startsWith("--target-directory=") && + canonical_path(command.argv[target].substring(19)) == "/etc/sudoers.d" + ) || + ( + command.argv[target].matches("^-[^-]*t.+$") && + canonical_path(command.argv[target].substring(command.argv[target].indexOf("t") + 1)) == "/etc/sudoers.d" + ) + ) && + !lists.range(command.argv.size()).exists(k, + k > target && + ( + command.argv[k] in ["-t", "--target-directory"] || + command.argv[k].startsWith("--target-directory=") || + command.argv[k].matches("^-[^-]*t.+$") + ) + ) + ) && + lists.range(command.argv.size()).exists(i, + i > 0 && + !command.argv[i].startsWith("-") && + (i == 1 || !command.argv[i - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) + ) + ) ) ) ) diff --git a/rules/release_precision_policy_test.go b/rules/release_precision_policy_test.go index ef36a18..80f800f 100644 --- a/rules/release_precision_policy_test.go +++ b/rules/release_precision_policy_test.go @@ -19,15 +19,22 @@ func TestReleasePrecisionPrivilegePolicy(t *testing.T) { {"Windows protected path in an unrelated value stays quiet", cmd(`Write-Output C:\Windows\System32\config\SAM /grant Everyone:F`), ""}, {"visudo validation is not a modification", cmd("visudo -c"), ""}, {"combined visudo validation flags are not a modification", cmd("visudo -cf /etc/sudoers"), ""}, + {"clustered attached visudo validation is not a modification", cmd("visudo -qcf/etc/sudoers"), ""}, {"visudo help is not a modification", cmd("visudo --help"), ""}, {"visudo export is not a modification", cmd("visudo --export=/tmp/sudoers.json"), ""}, + {"short visudo export is not a modification", cmd("visudo -f /etc/sudoers -x /tmp/sudoers.json"), ""}, {"visudo against a repository fixture stays quiet", cmd("visudo -f ./testdata/sudoers"), ""}, + {"short visudo file argument beginning with equals stays relative", cmd("visudo -f=/etc/sudoers"), ""}, {"sudoers backup path stays quiet", cmd("rm /etc/sudoers.backup"), ""}, {"repository sudoers structured write stays quiet", write("/repo/etc/sudoers"), ""}, {"active sudoers structured delete", model.Event{EventType: model.EventFileDelete, FilePath: "/etc/sudoers"}, "privilege.sudoers_tamper"}, {"sudoers redirect through proc task-root alias", cmd("printf policy > /proc/4321/task/8765/root/etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, {"sudoers PowerShell WhatIf still records intent", cmd("Set-Content /etc/sudoers test -WhatIf"), "privilege.sudoers_tamper"}, {"visudo against active policy", cmd("visudo --file=/etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, + {"visudo attached short file against active policy", cmd("visudo -f/etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, + {"visudo clustered file option against active policy", cmd("visudo -qf /etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, + {"visudo clustered attached file against active policy", cmd("visudo -qf/etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, + {"visudo clustered edit options target default policy", cmd("visudo -qs"), "privilege.sudoers_tamper"}, {"visudo combined file through proc task-root alias", cmd("visudo --file=/proc/4321/task/8765/root/etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, {"visudo combined file through traversal alias", cmd("visudo --file=/etc/numbat/../sudoers"), "privilege.sudoers_tamper"}, {"later visudo validation cannot suppress active policy edit", cmd("visudo --file=/etc/sudoers.d/agent; visudo -c"), "privilege.sudoers_tamper"}, @@ -35,6 +42,16 @@ func TestReleasePrecisionPrivilegePolicy(t *testing.T) { {"quoted sudoers fixture is not a modification", cmd(`echo "tee /etc/sudoers.d/agent"`), ""}, {"quoted sudoers example cannot suppress a real mutation", cmd(`printf policy > /etc/sudoers.d/agent; echo "printf example > /etc/sudoers"`), "privilege.sudoers_tamper"}, {"sudoers copy target", cmd("cp /tmp/policy /etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, + {"sudoers copy target before trailing option", cmd("cp /tmp/policy /etc/sudoers.d/agent --force"), "privilege.sudoers_tamper"}, + {"sudoers copy target directory", cmd("cp -t /etc/sudoers.d /tmp/agent"), "privilege.sudoers_tamper"}, + {"sudoers copy inline target directory", cmd("cp --target-directory=/proc/self/root/etc/sudoers.d /tmp/agent"), "privilege.sudoers_tamper"}, + {"sudoers copy positional target directory", cmd("cp /tmp/agent /etc/sudoers.d"), "privilege.sudoers_tamper"}, + {"target directory option value is not a copy source", cmd("cp -t /etc/sudoers.d --suffix agent"), ""}, + {"sudoers copy source before trailing option stays quiet", cmd("cp /etc/sudoers /tmp/policy --force"), ""}, + {"sudoers copy source with target directory stays quiet", cmd("cp -t /tmp /etc/sudoers"), ""}, + {"later target-directory option overrides apparent sudoers destination", cmd("cp /tmp/policy /etc/sudoers --target-directory /tmp"), ""}, + {"later target-directory option overrides protected target directory", cmd("cp -t /etc/sudoers.d -t /tmp /tmp/agent"), ""}, + {"last protected target-directory option wins", cmd("cp -t /tmp -t /etc/sudoers.d /tmp/agent"), "privilege.sudoers_tamper"}, {"moving active sudoers away is tampering", cmd("mv /etc/sudoers /tmp/sudoers"), "privilege.sudoers_tamper"}, {"moving active sudoers away with PowerShell is tampering", cmd("Move-Item -Path /etc/sudoers -Destination /tmp/sudoers"), "privilege.sudoers_tamper"}, {"sudoers path used as PowerShell value stays quiet", cmd(`Set-Content -Path /tmp/note -Value '/etc/sudoers.d/agent'`), ""}, @@ -113,6 +130,17 @@ func TestReleasePrecisionPersistencePolicy(t *testing.T) { {"authorized keys is the PowerShell copy source stays quiet", cmd(`Copy-Item ~/.ssh/authorized_keys /tmp/keys`), ""}, {"authorized keys is the named PowerShell copy source stays quiet", cmd(`Copy-Item -Path ~/.ssh/authorized_keys /tmp/keys`), ""}, {"authorized keys is the PowerShell copy destination", cmd(`Copy-Item /tmp/keys ~/.ssh/authorized_keys`), "persistence.ssh_authorized_keys_command"}, + {"authorized keys copy target before trailing option", cmd(`cp /tmp/keys ~/.ssh/authorized_keys --force`), "persistence.ssh_authorized_keys_command"}, + {"authorized keys copy into positional directory", cmd(`cp /tmp/authorized_keys ~/.ssh`), "persistence.ssh_authorized_keys_command"}, + {"authorized keys copy into target directory", cmd(`cp -t ~/.ssh /tmp/authorized_keys`), "persistence.ssh_authorized_keys_command"}, + {"later target-directory option overrides ssh destination", cmd(`cp --target-directory=~/.ssh --target-directory=/tmp /tmp/authorized_keys`), ""}, + {"last ssh target-directory option wins", cmd(`cp --target-directory=/tmp --target-directory=~/.ssh /tmp/authorized_keys`), "persistence.ssh_authorized_keys_command"}, + {"unrelated file copied into ssh directory stays quiet", cmd(`cp /tmp/notes ~/.ssh`), ""}, + {"backup suffix is not an authorized keys source", cmd(`cp -t ~/.ssh --backup --suffix authorized_keys /tmp/notes`), ""}, + {"authorized keys copy source before trailing option stays quiet", cmd(`cp ~/.ssh/authorized_keys /tmp/keys --force`), ""}, + {"authorized keys copy source with target directory stays quiet", cmd(`cp -t /tmp ~/.ssh/authorized_keys`), ""}, + {"moving authorized keys away is a mutation", cmd(`mv ~/.ssh/authorized_keys /tmp/keys`), "persistence.ssh_authorized_keys_command"}, + {"Windows drive root traversal reaches administrator authorized keys", write(`C:\..\ProgramData\ssh\administrators_authorized_keys`), "persistence.ssh_authorized_keys"}, {"commandless authorized-keys redirect", cmd("> ~/.ssh/authorized_keys"), "persistence.ssh_authorized_keys_command"}, {"quoted authorized-keys example cannot suppress a real mutation", cmd(`printf key > ~/.ssh/authorized_keys; echo "printf example > ~/.ssh/authorized_keys"`), "persistence.ssh_authorized_keys_command"}, {"commandless git-hook redirect", cmd("> .git/hooks/pre-commit"), "persistence.git_hook_write"}, From 9a9187b01eb0ac4cd55f978756a72e1e8800796c Mon Sep 17 00:00:00 2001 From: ron Date: Mon, 31 Aug 2026 21:59:21 +0000 Subject: [PATCH 8/9] fix(rule): enforce safe POSIX compound lists --- cmd/numbat/hook_enforce_test.go | 24 ++++++ docs/enforcement.md | 15 ++-- docs/rules.md | 17 +++-- internal/rule/engine_test.go | 18 ++++- internal/rule/shell_enforcement.go | 117 ++++++++++++++++++++++++++++- 5 files changed, 174 insertions(+), 17 deletions(-) diff --git a/cmd/numbat/hook_enforce_test.go b/cmd/numbat/hook_enforce_test.go index 5304474..2ce51a3 100644 --- a/cmd/numbat/hook_enforce_test.go +++ b/cmd/numbat/hook_enforce_test.go @@ -163,6 +163,20 @@ expr: | ) ` +const gitPushEnforceRule = `id: enforce_test.git_push +version: "1.0" +title: Git push +severity: high +enforce: true +expr: |- + event.event_type == "command.exec" && + shell_commands.exists(command, + command.name == "git" && + command.argv.size() > 1 && + command.argv[1] == "push" + ) +` + const mediumEnforceRule = `id: enforce_test.medium_block version: "1.0" title: Medium operator policy match @@ -1424,6 +1438,16 @@ func TestCodexEnforceShellMatchDenies(t *testing.T) { } } +func TestCodexEnforceCompoundShellListDenies(t *testing.T) { + dir := writeEnforceRuleFile(t, gitPushEnforceRule) + payload := `{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"gh repo fork owner/repo && git push origin main"},"cwd":"/proj","session_id":"s1"}` + out, errOut, code := runCLIStdin(payload, + enforceHookArgs(t, "hook", "PreToolUse", "--agent", "codex", "--enforce", "--rules-dir", dir)...) + if code != 0 || decodeDecision(t, out) != model.EnforcementDecisionDeny { + t.Fatalf("compound command was not denied: exit=%d stdout=%q stderr=%q", code, out, errOut) + } +} + func TestBuiltinDestructiveDetectionsRemainMonitorOnly(t *testing.T) { for _, tc := range []struct { agent string diff --git a/docs/enforcement.md b/docs/enforcement.md index 680c25e..f23801e 100644 --- a/docs/enforcement.md +++ b/docs/enforcement.md @@ -79,17 +79,20 @@ depends on executable command semantics. Detection parses a broad set of shell structures. When a match is derived from `shell_commands`, blocking uses a smaller static subset: -- POSIX shells: one simple command or one pipeline of simple commands +- POSIX shells: static simple commands, pipelines, groups, and subshells joined + by `;`, `&&`, or `||` - PowerShell and `cmd.exe`: one simple command - supported transparent launchers, only when the final child command meets the same rules Every projected command must have static arguments, assignments, and redirect -targets. Multiple statements, conditionals, loops, shell background syntax, -substitutions, same-script functions, inline child interpreters, `eval`, -`Invoke-Expression`, PowerShell or CMD pipelines, previews, parser diagnostics, -and truncated projections stay detection-only. This event-wide gate avoids -denying an action based on a command that may not execute. +targets. A POSIX `&&` or `||` list is eligible when either branch may execute; +if a literal `true`, `false`, or `:` makes a branch statically unreachable, the +whole shell program stays detection-only. Conditionals, loops, shell background +syntax, substitutions, same-script functions, inline child interpreters, +`eval`, `Invoke-Expression`, PowerShell or CMD compound commands, previews, +parser diagnostics, and truncated projections also stay detection-only. This +event-wide gate avoids denying an action based on a command that cannot execute. numbat recognizes explicit `-WhatIf` and a statically visible `$WhatIfPreference = $true` for known cmdlets. It does not infer ambient diff --git a/docs/rules.md b/docs/rules.md index 3c63fa2..a52e7a1 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -398,15 +398,16 @@ module-qualified forms. Ambient preference and command-resolution state are not inferred. For a shell-derived match, blocking has a narrower eligibility boundary than -detection: the complete shell program must be one static simple command or one -static POSIX pipeline. Supported transparent launchers are allowed only when -their final child command is also in that subset. Multiple statements, control -flow, same-script functions, inline child interpreters, `eval` or +detection. POSIX simple commands, pipelines, static groups and subshells may be +combined with `;`, `&&`, or `||`; statically unreachable short-circuit branches +remain detection-only. Supported transparent launchers are allowed only when +their final child command is also in that subset. Other control flow, +same-script functions, inline child interpreters, `eval` or `Invoke-Expression`, substitutions, runtime-dependent values, PowerShell or CMD -pipelines, previews, parser diagnostics, and truncated projections remain -detection-only. A rule may still use `shell_commands` alongside structured -fields such as `event.file_path`; a matching commandless structured event does -not require a shell projection. See [Enforcement](enforcement.md). +compound commands, previews, parser diagnostics, and truncated projections +remain detection-only. A rule may still use `shell_commands` alongside +structured fields such as `event.file_path`; a matching commandless structured +event does not require a shell projection. See [Enforcement](enforcement.md). ## Enforcement rules diff --git a/internal/rule/engine_test.go b/internal/rule/engine_test.go index bfe96cb..e3e0d75 100644 --- a/internal/rule/engine_test.go +++ b/internal/rule/engine_test.go @@ -587,7 +587,12 @@ func TestEngineLimitsEnforcementToStaticShellSubset(t *testing.T) { allowed := []model.Event{ {EventType: model.EventCommandExec, ToolName: "bash", Command: `wipefs -a /dev/sda`}, + {EventType: model.EventCommandExec, ToolName: "bash", Command: `echo ready; wipefs -a /dev/sda`}, + {EventType: model.EventCommandExec, ToolName: "bash", Command: `echo ready && wipefs -a /dev/sda`}, + {EventType: model.EventCommandExec, ToolName: "bash", Command: `echo ready || wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `printf x | wipefs -a /dev/sda`}, + {EventType: model.EventCommandExec, ToolName: "bash", Command: `(wipefs -a /dev/sda)`}, + {EventType: model.EventCommandExec, ToolName: "bash", Command: `{ wipefs -a /dev/sda; }`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `sudo wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `sudo MODE=test wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `sudo MODE=test -- wipefs -a /dev/sda`}, @@ -648,9 +653,9 @@ func TestEngineLimitsEnforcementToStaticShellSubset(t *testing.T) { {EventType: model.EventCommandExec, ToolName: "bash", Command: `exec -a wipe wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `pwsh -Command 'Stop-Process -Name target -Force'`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `echo "$(wipefs -a /dev/sda)"`}, + {EventType: model.EventCommandExec, ToolName: "bash", Command: `{ wipefs -a /dev/sda; } > "$output"`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `wipefs -a /dev/sda --no-*`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `wipefs -a /dev/sda --{no-act,other}`}, - {EventType: model.EventCommandExec, ToolName: "bash", Command: `echo ready; wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `"$next"; wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `! wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `wipefs -a /dev/sda &`}, @@ -681,6 +686,17 @@ func TestEngineLimitsEnforcementToStaticShellSubset(t *testing.T) { } } + for _, command := range []string{ + `echo ready; git status`, + `gh repo view owner/repo && git status`, + `gh repo view owner/repo || git status`, + } { + matches, err := eng.Eval(model.Event{EventType: model.EventCommandExec, ToolName: "bash", Command: command}) + if err != nil || len(matches) != 0 { + t.Errorf("Eval(%q) = (%+v, %v), want no read-only match", command, matches, err) + } + } + for _, event := range []model.Event{ {EventType: model.EventCommandExec, ToolName: "bash", Command: `sudo -- MODE=test wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `sudo --other-user root wipefs -a /dev/sda`}, diff --git a/internal/rule/shell_enforcement.go b/internal/rule/shell_enforcement.go index 098de3e..1f15279 100644 --- a/internal/rule/shell_enforcement.go +++ b/internal/rule/shell_enforcement.go @@ -7,7 +7,19 @@ import ( ) func posixEnforcementShapeSafe(file *syntax.File) bool { - return file != nil && len(file.Stmts) == 1 && posixStatementEnforcementSafe(file.Stmts[0]) + return file != nil && posixStatementListEnforcementSafe(file.Stmts) +} + +func posixStatementListEnforcementSafe(statements []*syntax.Stmt) bool { + if len(statements) == 0 { + return false + } + for _, statement := range statements { + if !posixStatementEnforcementSafe(statement) { + return false + } + } + return true } func posixStatementEnforcementSafe(stmt *syntax.Stmt) bool { @@ -18,6 +30,14 @@ func posixStatementEnforcementSafe(stmt *syntax.Stmt) bool { if redirect.Hdoc != nil { return false } + if redirect.Word != nil { + if _, static := staticWord(redirect.Word); !static { + return false + } + } + if redirect.N != nil && strings.Trim(redirect.N.Value, "0123456789") != "" { + return false + } } switch command := stmt.Cmd.(type) { case nil: @@ -25,16 +45,109 @@ func posixStatementEnforcementSafe(stmt *syntax.Stmt) bool { case *syntax.CallExpr: return true case *syntax.BinaryCmd: - if command.Op != syntax.Pipe && command.Op != syntax.PipeAll { + switch command.Op { + case syntax.Pipe, syntax.PipeAll: + case syntax.AndStmt: + if success, known := posixStaticSuccess(command.X); known && !success { + return false + } + case syntax.OrStmt: + if success, known := posixStaticSuccess(command.X); known && success { + return false + } + default: return false } return posixStatementEnforcementSafe(command.X) && posixStatementEnforcementSafe(command.Y) + case *syntax.Subshell: + return posixStatementListEnforcementSafe(command.Stmts) + case *syntax.Block: + return posixStatementListEnforcementSafe(command.Stmts) default: return false } } +// posixStaticSuccess recognizes only shell builtins whose status cannot depend +// on arguments or the environment. It is used to keep statically unreachable +// short-circuit branches detection-only; unknown statuses remain eligible +// because either branch may execute at runtime. +func posixStaticSuccess(stmt *syntax.Stmt) (bool, bool) { + if stmt == nil || stmt.Negated || stmt.Background || stmt.Coprocess || stmt.Disown { + return false, false + } + success, known := posixCommandStaticSuccess(stmt.Cmd) + if !known || len(stmt.Redirs) > 0 && success { + return false, false + } + return success, true +} + +func posixCommandStaticSuccess(command syntax.Command) (bool, bool) { + switch command := command.(type) { + case *syntax.CallExpr: + if len(command.Args) == 0 || len(command.Assigns) > 0 { + return false, false + } + name, ok := staticWord(command.Args[0]) + if !ok || name != commandProgram(name) { + return false, false + } + switch name { + case ":", "true": + return true, true + case "false": + return false, true + default: + return false, false + } + case *syntax.BinaryCmd: + left, leftKnown := posixStaticSuccess(command.X) + switch command.Op { + case syntax.AndStmt: + if leftKnown && !left { + return false, true + } + right, rightKnown := posixStaticSuccess(command.Y) + if leftKnown && left { + return right, rightKnown + } + if rightKnown && !right { + return false, true + } + return false, false + case syntax.OrStmt: + if leftKnown && left { + return true, true + } + right, rightKnown := posixStaticSuccess(command.Y) + if leftKnown && !left { + return right, rightKnown + } + if rightKnown && right { + return true, true + } + return false, false + default: + return false, false + } + case *syntax.Subshell: + return posixStaticListSuccess(command.Stmts) + case *syntax.Block: + return posixStaticListSuccess(command.Stmts) + default: + return false, false + } +} + +func posixStaticListSuccess(statements []*syntax.Stmt) (bool, bool) { + if len(statements) == 0 { + return false, false + } + return posixStaticSuccess(statements[len(statements)-1]) +} + func powerShellEnforcementShapeSafe(source string) bool { var singleQuoted, doubleQuoted bool for i := 0; i < len(source); i++ { From b98d8f988048cd7c886832e6763b40a4cbf76c4b Mon Sep 17 00:00:00 2001 From: Adel Ka Date: Wed, 16 Sep 2026 08:58:00 +1000 Subject: [PATCH 9/9] fix(rule): narrow protected path hardening --- cmd/numbat/hook_enforce_test.go | 24 - docs/enforcement.md | 15 +- docs/rules.md | 23 +- internal/rule/canonical_path.go | 81 ++ internal/rule/canonical_path_test.go | 48 +- internal/rule/engine.go | 37 +- internal/rule/engine_test.go | 18 +- internal/rule/shell_enforcement.go | 117 +-- ...f6be9adb8bc87fda5a3148c8f3254f21c3c1b73.pb | 870 ----------------- ...ad0f093f09d1c7fe04c77e58f5e115d356f9ccb.pb | 899 ------------------ ...010ffaa2fc29993e28f0203fbfd0b270ca296a6.pb | Bin 0 -> 21912 bytes ...12927ae303b766563660e10f9a6088495bfdec2.pb | Bin 0 -> 10925 bytes .../ssh_authorized_keys_command.yaml | 149 +-- rules/privilege/sudoers_tamper.yaml | 106 +-- rules/release_precision_policy_test.go | 27 +- 15 files changed, 171 insertions(+), 2243 deletions(-) create mode 100644 internal/rule/canonical_path.go delete mode 100644 rules/internal/checked/0378d920be6102465633215dcf6be9adb8bc87fda5a3148c8f3254f21c3c1b73.pb delete mode 100644 rules/internal/checked/7f5aef9eb4a0b5fe04a85b8dcad0f093f09d1c7fe04c77e58f5e115d356f9ccb.pb create mode 100644 rules/internal/checked/8008a7f1d1a8caeebe5c0137d010ffaa2fc29993e28f0203fbfd0b270ca296a6.pb create mode 100644 rules/internal/checked/e6634233a808b79f131e95b5a12927ae303b766563660e10f9a6088495bfdec2.pb diff --git a/cmd/numbat/hook_enforce_test.go b/cmd/numbat/hook_enforce_test.go index 2ce51a3..5304474 100644 --- a/cmd/numbat/hook_enforce_test.go +++ b/cmd/numbat/hook_enforce_test.go @@ -163,20 +163,6 @@ expr: | ) ` -const gitPushEnforceRule = `id: enforce_test.git_push -version: "1.0" -title: Git push -severity: high -enforce: true -expr: |- - event.event_type == "command.exec" && - shell_commands.exists(command, - command.name == "git" && - command.argv.size() > 1 && - command.argv[1] == "push" - ) -` - const mediumEnforceRule = `id: enforce_test.medium_block version: "1.0" title: Medium operator policy match @@ -1438,16 +1424,6 @@ func TestCodexEnforceShellMatchDenies(t *testing.T) { } } -func TestCodexEnforceCompoundShellListDenies(t *testing.T) { - dir := writeEnforceRuleFile(t, gitPushEnforceRule) - payload := `{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"gh repo fork owner/repo && git push origin main"},"cwd":"/proj","session_id":"s1"}` - out, errOut, code := runCLIStdin(payload, - enforceHookArgs(t, "hook", "PreToolUse", "--agent", "codex", "--enforce", "--rules-dir", dir)...) - if code != 0 || decodeDecision(t, out) != model.EnforcementDecisionDeny { - t.Fatalf("compound command was not denied: exit=%d stdout=%q stderr=%q", code, out, errOut) - } -} - func TestBuiltinDestructiveDetectionsRemainMonitorOnly(t *testing.T) { for _, tc := range []struct { agent string diff --git a/docs/enforcement.md b/docs/enforcement.md index f23801e..680c25e 100644 --- a/docs/enforcement.md +++ b/docs/enforcement.md @@ -79,20 +79,17 @@ depends on executable command semantics. Detection parses a broad set of shell structures. When a match is derived from `shell_commands`, blocking uses a smaller static subset: -- POSIX shells: static simple commands, pipelines, groups, and subshells joined - by `;`, `&&`, or `||` +- POSIX shells: one simple command or one pipeline of simple commands - PowerShell and `cmd.exe`: one simple command - supported transparent launchers, only when the final child command meets the same rules Every projected command must have static arguments, assignments, and redirect -targets. A POSIX `&&` or `||` list is eligible when either branch may execute; -if a literal `true`, `false`, or `:` makes a branch statically unreachable, the -whole shell program stays detection-only. Conditionals, loops, shell background -syntax, substitutions, same-script functions, inline child interpreters, -`eval`, `Invoke-Expression`, PowerShell or CMD compound commands, previews, -parser diagnostics, and truncated projections also stay detection-only. This -event-wide gate avoids denying an action based on a command that cannot execute. +targets. Multiple statements, conditionals, loops, shell background syntax, +substitutions, same-script functions, inline child interpreters, `eval`, +`Invoke-Expression`, PowerShell or CMD pipelines, previews, parser diagnostics, +and truncated projections stay detection-only. This event-wide gate avoids +denying an action based on a command that may not execute. numbat recognizes explicit `-WhatIf` and a statically visible `$WhatIfPreference = $true` for known cmdlets. It does not infer ambient diff --git a/docs/rules.md b/docs/rules.md index a52e7a1..e3776e9 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -140,7 +140,7 @@ Common CEL operations include: | Boolean logic | `a && b`, `a || b`, `!a` | | Membership | `value in ["a", "b"]` | | String tests | `contains`, `startsWith`, `endsWith`, `matches` | -| String slicing | `substring(start)`, `substring(start, end)` | +| String position/slicing | `indexOf`, `substring(start)`, `substring(start, end)` | | List predicates | `exists`, `all`, `exists_one` | | List range | `items.slice(start, end)` | | Integer indexes | `lists.range(n).exists(i, ...)` | @@ -153,8 +153,8 @@ escaping; for example, a literal dot is written as `"\\.env"`. `canonical_path(p)` normalizes path separators, `.`, `..`, and duplicate `/` segments. It treats each leading `/proc//root` or `/proc//task//root` as `/`. Relative paths remain relative. It -keeps absolute Windows paths rooted to their drive. It does not access the -filesystem or resolve other symbolic links. +keeps Windows drive and UNC roots intact. It does not access the filesystem or +resolve other symbolic links. Action types are alternatives, not layers. A recognized shell action is a `command.exec`, not both a `tool.call` and a `command.exec`; file and network @@ -398,16 +398,15 @@ module-qualified forms. Ambient preference and command-resolution state are not inferred. For a shell-derived match, blocking has a narrower eligibility boundary than -detection. POSIX simple commands, pipelines, static groups and subshells may be -combined with `;`, `&&`, or `||`; statically unreachable short-circuit branches -remain detection-only. Supported transparent launchers are allowed only when -their final child command is also in that subset. Other control flow, -same-script functions, inline child interpreters, `eval` or +detection: the complete shell program must be one static simple command or one +static POSIX pipeline. Supported transparent launchers are allowed only when +their final child command is also in that subset. Multiple statements, control +flow, same-script functions, inline child interpreters, `eval` or `Invoke-Expression`, substitutions, runtime-dependent values, PowerShell or CMD -compound commands, previews, parser diagnostics, and truncated projections -remain detection-only. A rule may still use `shell_commands` alongside -structured fields such as `event.file_path`; a matching commandless structured -event does not require a shell projection. See [Enforcement](enforcement.md). +pipelines, previews, parser diagnostics, and truncated projections remain +detection-only. A rule may still use `shell_commands` alongside structured +fields such as `event.file_path`; a matching commandless structured event does +not require a shell projection. See [Enforcement](enforcement.md). ## Enforcement rules diff --git a/internal/rule/canonical_path.go b/internal/rule/canonical_path.go new file mode 100644 index 0000000..e4bf204 --- /dev/null +++ b/internal/rule/canonical_path.go @@ -0,0 +1,81 @@ +package rule + +import ( + "path" + "regexp" + "strings" + + "github.com/google/cel-go/common/types" + "github.com/google/cel-go/common/types/ref" + + "github.com/perplexityai/numbat/internal/model" +) + +var procRootPath = regexp.MustCompile(`^/proc/(?:(?:self|[1-9][0-9]*)/task/[1-9][0-9]*|self|thread-self|[1-9][0-9]*)/root(?:/+|$)`) + +func canonicalPathBinding(arg ref.Val) ref.Val { + value, ok := arg.(types.String) + if !ok { + return types.MaybeNoSuchOverloadErr(arg) + } + return types.String(canonicalPath(string(value))) +} + +func canonicalPath(value string) string { + value = model.NormalizeEventPath(value) + if value == "" { + return "" + } + if len(value) >= 7 && value[:4] == "//?/" && isWindowsDrivePath(value[4:]) { + value = value[4:] + } else if len(value) >= 8 && strings.EqualFold(value[:8], "//?/UNC/") { + value = "//" + value[8:] + } + if len(value) > 2 && strings.HasPrefix(value, "//") && value[2] != '/' { + return canonicalUNCPath(value) + } + volume := "" + if isWindowsDrivePath(value) { + volume = value[:2] + value = value[2:] + } + for { + if volume == "" { + prefix := procRootPath.FindStringIndex(value) + if prefix != nil { + if prefix[1] == len(value) { + return "/" + } + value = value[prefix[1]-1:] + continue + } + } + clean := path.Clean(value) + if clean == value { + return volume + clean + } + value = clean + } +} + +func isWindowsDrivePath(value string) bool { + return len(value) >= 3 && + ((value[0] >= 'A' && value[0] <= 'Z') || (value[0] >= 'a' && value[0] <= 'z')) && + value[1] == ':' && value[2] == '/' +} + +func canonicalUNCPath(value string) string { + parts := strings.SplitN(strings.TrimPrefix(value, "//"), "/", 3) + if len(parts) < 2 || parts[0] == "" || parts[1] == "" { + return value + } + root := "//" + parts[0] + "/" + parts[1] + if len(parts) == 2 { + return root + } + rest := path.Clean("/" + parts[2]) + if rest == "/" { + return root + } + return root + rest +} diff --git a/internal/rule/canonical_path_test.go b/internal/rule/canonical_path_test.go index 5b2ac4e..a3c4173 100644 --- a/internal/rule/canonical_path_test.go +++ b/internal/rule/canonical_path_test.go @@ -32,7 +32,9 @@ func TestCanonicalPathCollapsesTraversalForFileEvents(t *testing.T) { {"proc_root_parent_traversal", "/proc/self/root/../etc/numbat/rules/protect_numbat.yaml", true}, {"proc_root_dot_prefix", "/proc/./self/root/etc/numbat/rules/protect_numbat.yaml", true}, {"duplicate_slash", "/etc/numbat//rules/protect_numbat.yaml", true}, + {"three_leading_slashes", "///etc/numbat/rules/protect_numbat.yaml", true}, {"windows_separators", `\etc\numbat\rules\protect_numbat.yaml`, true}, + {"UNC_path_does_not_become_local", `\\etc\numbat\rules\protect_numbat.yaml`, false}, {"zero_is_not_a_pid", "/proc/0/root/etc/numbat/rules/protect_numbat.yaml", false}, {"zero_padded_pid", "/proc/04321/root/etc/numbat/rules/protect_numbat.yaml", false}, {"task_zero_tid", "/proc/4321/task/0/root/etc/numbat/rules/protect_numbat.yaml", false}, @@ -86,17 +88,43 @@ func TestCanonicalPathPreservesWindowsDriveRoot(t *testing.T) { Severity: model.SeverityHigh, Expr: `canonical_path(event.file_path) == "C:/ProgramData/ssh/administrators_authorized_keys"`, }) - ev := model.Event{ - EventID: "e", - EventType: model.EventFileWrite, - FilePath: `C:\..\ProgramData\ssh\administrators_authorized_keys`, + for _, filePath := range []string{ + `C:\..\ProgramData\ssh\administrators_authorized_keys`, + `\\?\C:\ProgramData\ssh\administrators_authorized_keys`, + } { + ev := model.Event{ + EventID: "e", + EventType: model.EventFileWrite, + FilePath: filePath, + } + matches, err := eng.Eval(ev) + if err != nil { + t.Fatalf("Eval(%q): %v", filePath, err) + } + if len(matches) != 1 { + t.Fatalf("Eval(%q) matches = %d, want 1", filePath, len(matches)) + } } - matches, err := eng.Eval(ev) - if err != nil { - t.Fatalf("Eval: %v", err) - } - if len(matches) != 1 { - t.Fatalf("matches = %d, want 1", len(matches)) +} + +func TestCanonicalPathPreservesUNCRoot(t *testing.T) { + eng := mustEngine(t, Rule{ + ID: "t.unc_root", + Severity: model.SeverityLow, + Expr: `canonical_path(event.file_path) == "//server/share/keys/authorized_keys"`, + }) + for _, filePath := range []string{ + `\\server\share\keys\old\..\authorized_keys`, + `\\?\UNC\server\share\keys\authorized_keys`, + `//server/share/../../keys/authorized_keys`, + } { + matches, err := eng.Eval(model.Event{EventType: model.EventFileWrite, FilePath: filePath}) + if err != nil { + t.Fatalf("Eval(%q): %v", filePath, err) + } + if len(matches) != 1 { + t.Fatalf("Eval(%q) matches = %d, want 1", filePath, len(matches)) + } } } diff --git a/internal/rule/engine.go b/internal/rule/engine.go index 55ad290..91749f4 100644 --- a/internal/rule/engine.go +++ b/internal/rule/engine.go @@ -3,9 +3,7 @@ package rule import ( "errors" "fmt" - "path" "reflect" - "regexp" "strings" "time" "unicode" @@ -53,37 +51,6 @@ type compiledExpression struct { const contentRuleCostLimit uint64 = 10_000_000 -var procRootPath = regexp.MustCompile(`^/proc/(?:(?:self|[1-9][0-9]*)/task/[1-9][0-9]*|self|thread-self|[1-9][0-9]*)/root(?:/+|$)`) - -func canonicalPath(value string) string { - value = model.NormalizeEventPath(value) - if value == "" { - return "" - } - volume := "" - if len(value) >= 3 && ((value[0] >= 'A' && value[0] <= 'Z') || (value[0] >= 'a' && value[0] <= 'z')) && value[1] == ':' && value[2] == '/' { - volume = value[:2] - value = value[2:] - } - for { - if volume == "" { - prefix := procRootPath.FindStringIndex(value) - if prefix != nil { - if prefix[1] == len(value) { - return "/" - } - value = value[prefix[1]-1:] - continue - } - } - clean := path.Clean(value) - if clean == value { - return volume + clean - } - value = clean - } -} - // SequenceRule is a compiled sequence rule ready for per-step evaluation. It // distills the validated spec (window, cap) next to the compiled step // programs so a tracker never re-parses YAML fields on the hot path. @@ -156,9 +123,7 @@ func newEnv() (*cel.Env, error) { cel.Function("canonical_path", cel.Overload("canonical_path_string", []*cel.Type{cel.StringType}, cel.StringType, - cel.UnaryBinding(func(arg ref.Val) ref.Val { - return types.String(canonicalPath(string(arg.(types.String)))) - }), + cel.UnaryBinding(canonicalPathBinding), ), ), ) diff --git a/internal/rule/engine_test.go b/internal/rule/engine_test.go index e3e0d75..bfe96cb 100644 --- a/internal/rule/engine_test.go +++ b/internal/rule/engine_test.go @@ -587,12 +587,7 @@ func TestEngineLimitsEnforcementToStaticShellSubset(t *testing.T) { allowed := []model.Event{ {EventType: model.EventCommandExec, ToolName: "bash", Command: `wipefs -a /dev/sda`}, - {EventType: model.EventCommandExec, ToolName: "bash", Command: `echo ready; wipefs -a /dev/sda`}, - {EventType: model.EventCommandExec, ToolName: "bash", Command: `echo ready && wipefs -a /dev/sda`}, - {EventType: model.EventCommandExec, ToolName: "bash", Command: `echo ready || wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `printf x | wipefs -a /dev/sda`}, - {EventType: model.EventCommandExec, ToolName: "bash", Command: `(wipefs -a /dev/sda)`}, - {EventType: model.EventCommandExec, ToolName: "bash", Command: `{ wipefs -a /dev/sda; }`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `sudo wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `sudo MODE=test wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `sudo MODE=test -- wipefs -a /dev/sda`}, @@ -653,9 +648,9 @@ func TestEngineLimitsEnforcementToStaticShellSubset(t *testing.T) { {EventType: model.EventCommandExec, ToolName: "bash", Command: `exec -a wipe wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `pwsh -Command 'Stop-Process -Name target -Force'`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `echo "$(wipefs -a /dev/sda)"`}, - {EventType: model.EventCommandExec, ToolName: "bash", Command: `{ wipefs -a /dev/sda; } > "$output"`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `wipefs -a /dev/sda --no-*`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `wipefs -a /dev/sda --{no-act,other}`}, + {EventType: model.EventCommandExec, ToolName: "bash", Command: `echo ready; wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `"$next"; wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `! wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `wipefs -a /dev/sda &`}, @@ -686,17 +681,6 @@ func TestEngineLimitsEnforcementToStaticShellSubset(t *testing.T) { } } - for _, command := range []string{ - `echo ready; git status`, - `gh repo view owner/repo && git status`, - `gh repo view owner/repo || git status`, - } { - matches, err := eng.Eval(model.Event{EventType: model.EventCommandExec, ToolName: "bash", Command: command}) - if err != nil || len(matches) != 0 { - t.Errorf("Eval(%q) = (%+v, %v), want no read-only match", command, matches, err) - } - } - for _, event := range []model.Event{ {EventType: model.EventCommandExec, ToolName: "bash", Command: `sudo -- MODE=test wipefs -a /dev/sda`}, {EventType: model.EventCommandExec, ToolName: "bash", Command: `sudo --other-user root wipefs -a /dev/sda`}, diff --git a/internal/rule/shell_enforcement.go b/internal/rule/shell_enforcement.go index 1f15279..098de3e 100644 --- a/internal/rule/shell_enforcement.go +++ b/internal/rule/shell_enforcement.go @@ -7,19 +7,7 @@ import ( ) func posixEnforcementShapeSafe(file *syntax.File) bool { - return file != nil && posixStatementListEnforcementSafe(file.Stmts) -} - -func posixStatementListEnforcementSafe(statements []*syntax.Stmt) bool { - if len(statements) == 0 { - return false - } - for _, statement := range statements { - if !posixStatementEnforcementSafe(statement) { - return false - } - } - return true + return file != nil && len(file.Stmts) == 1 && posixStatementEnforcementSafe(file.Stmts[0]) } func posixStatementEnforcementSafe(stmt *syntax.Stmt) bool { @@ -30,14 +18,6 @@ func posixStatementEnforcementSafe(stmt *syntax.Stmt) bool { if redirect.Hdoc != nil { return false } - if redirect.Word != nil { - if _, static := staticWord(redirect.Word); !static { - return false - } - } - if redirect.N != nil && strings.Trim(redirect.N.Value, "0123456789") != "" { - return false - } } switch command := stmt.Cmd.(type) { case nil: @@ -45,109 +25,16 @@ func posixStatementEnforcementSafe(stmt *syntax.Stmt) bool { case *syntax.CallExpr: return true case *syntax.BinaryCmd: - switch command.Op { - case syntax.Pipe, syntax.PipeAll: - case syntax.AndStmt: - if success, known := posixStaticSuccess(command.X); known && !success { - return false - } - case syntax.OrStmt: - if success, known := posixStaticSuccess(command.X); known && success { - return false - } - default: + if command.Op != syntax.Pipe && command.Op != syntax.PipeAll { return false } return posixStatementEnforcementSafe(command.X) && posixStatementEnforcementSafe(command.Y) - case *syntax.Subshell: - return posixStatementListEnforcementSafe(command.Stmts) - case *syntax.Block: - return posixStatementListEnforcementSafe(command.Stmts) default: return false } } -// posixStaticSuccess recognizes only shell builtins whose status cannot depend -// on arguments or the environment. It is used to keep statically unreachable -// short-circuit branches detection-only; unknown statuses remain eligible -// because either branch may execute at runtime. -func posixStaticSuccess(stmt *syntax.Stmt) (bool, bool) { - if stmt == nil || stmt.Negated || stmt.Background || stmt.Coprocess || stmt.Disown { - return false, false - } - success, known := posixCommandStaticSuccess(stmt.Cmd) - if !known || len(stmt.Redirs) > 0 && success { - return false, false - } - return success, true -} - -func posixCommandStaticSuccess(command syntax.Command) (bool, bool) { - switch command := command.(type) { - case *syntax.CallExpr: - if len(command.Args) == 0 || len(command.Assigns) > 0 { - return false, false - } - name, ok := staticWord(command.Args[0]) - if !ok || name != commandProgram(name) { - return false, false - } - switch name { - case ":", "true": - return true, true - case "false": - return false, true - default: - return false, false - } - case *syntax.BinaryCmd: - left, leftKnown := posixStaticSuccess(command.X) - switch command.Op { - case syntax.AndStmt: - if leftKnown && !left { - return false, true - } - right, rightKnown := posixStaticSuccess(command.Y) - if leftKnown && left { - return right, rightKnown - } - if rightKnown && !right { - return false, true - } - return false, false - case syntax.OrStmt: - if leftKnown && left { - return true, true - } - right, rightKnown := posixStaticSuccess(command.Y) - if leftKnown && !left { - return right, rightKnown - } - if rightKnown && right { - return true, true - } - return false, false - default: - return false, false - } - case *syntax.Subshell: - return posixStaticListSuccess(command.Stmts) - case *syntax.Block: - return posixStaticListSuccess(command.Stmts) - default: - return false, false - } -} - -func posixStaticListSuccess(statements []*syntax.Stmt) (bool, bool) { - if len(statements) == 0 { - return false, false - } - return posixStaticSuccess(statements[len(statements)-1]) -} - func powerShellEnforcementShapeSafe(source string) bool { var singleQuoted, doubleQuoted bool for i := 0; i < len(source); i++ { diff --git a/rules/internal/checked/0378d920be6102465633215dcf6be9adb8bc87fda5a3148c8f3254f21c3c1b73.pb b/rules/internal/checked/0378d920be6102465633215dcf6be9adb8bc87fda5a3148c8f3254f21c3c1b73.pb deleted file mode 100644 index 3303f62..0000000 --- a/rules/internal/checked/0378d920be6102465633215dcf6be9adb8bc87fda5a3148c8f3254f21c3c1b73.pb +++ /dev/null @@ -1,870 +0,0 @@ -  -event equals  -event equals   -event  equals   logical_and  -logical_or -shell_commands  -command - -redirect  in_listcanonical_path_string - -redirectmatches_string!  logical_and # -@result$  logical_not%not_strictly_false & -@result'  -logical_or ( -@result * -command,matches_string . -command2canonical_path_string 3 -arg4matches_string 7 -@result8  logical_not9not_strictly_false : -@result;  -logical_or < -@result>  logical_and?  logical_not @ -commandBmatches_string D -command H -argImatches_string L -@resultM  logical_notNnot_strictly_false O -@resultP  -logical_or Q -@resultS  -logical_orT  logical_andU  -logical_or V -commandXmatches_string[  lists_range \ -command^  list_sizea -ib greater_int64dcanonical_path_string e -commandg  -index_listh -iimatches_stringk  logical_andl -i mequals o -commandq  -index_listr -issubtract_int64umatches_stringw  -logical_ory  lists_rangez -i} -j ~equals -command  -index_list -jmatches_string  -logical_or -@resultnot_strictly_false -@result  logical_and -@result  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and  -logical_or -commandmatches_string -command  list_size greater_int64  logical_and  lists_range -command  list_size -i greater_int64canonical_path_string -command  -index_list -imatches_string  logical_and -commandmatches_string -commandmatches_string  logical_not -command - -arg in_list - -argstarts_with_string  -logical_or - -argmatches_string  -logical_or -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and -i equals -command  list_sizesubtract_int64 -command  -list_slice -i  add_int64 -command  list_size - -argmatches_string -@resultnot_strictly_false -@result  logical_and -@result  -logical_or  logical_and  -logical_or -commandmatches_string -command  -index_list -isubtract_int64matches_string  logical_not -command - -argmatches_string -@result  logical_notnot_strictly_false -@result  -logical_or -@result -i equals -command  list_sizesubtract_int64 -command  -list_slice -i  add_int64 -command  list_size - -argmatches_string -@resultnot_strictly_false -@result  logical_and -@result  -logical_or  logical_and  -logical_or  logical_and  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and  -logical_or -commandmatches_string -command  list_size greater_int64  logical_and  lists_range -command  list_size -i greater_int64canonical_path_string -command  -index_list -imatches_string  lists_range -command  list_size  -source greater_int64canonical_path_string -command  -index_list  -sourcematches_string  logical_and  -source equals  logical_not -command  -index_list  -sourcesubtract_int64matches_string  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_andcanonical_path_string -command  -index_list -imatches_string  lists_range -command  list_size  -source greater_int64canonical_path_string -command  -index_list  -sourcematches_string  logical_and  -source equals  logical_not -command  -index_list  -sourcesubtract_int64matches_string  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and  -logical_or  logical_and -command  -index_list -isubtract_int64matches_string  logical_not  lists_range -command  list_size -k greater_int64 -i -command  -index_list -k in_list -command  -index_list -kstarts_with_string  -logical_or -command  -index_list -kmatches_string  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and -i greater_int64  logical_not -command - -arg in_list - -argstarts_with_string  -logical_or - -argmatches_string  -logical_or -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and -command  -list_slice -i  add_int64 -command  list_size - -argmatches_string -@resultnot_strictly_false -@result  logical_and -@result  logical_and  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  lists_range -command  list_size  -target greater_int64 -command  -index_list  -targetstarts_with_stringcanonical_path_string -command  -index_list  -targetstring_substring_intmatches_string  lists_range -command  list_size  -source greater_int64canonical_path_string -command  -index_list  -sourcematches_string  logical_and  -source equals  logical_not -command  -index_list  -sourcesubtract_int64matches_string  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_andcanonical_path_string -command  -index_list  -targetstring_substring_intmatches_string  lists_range -command  list_size  -source greater_int64canonical_path_string -command  -index_list  -sourcematches_string  logical_and  -source equals  logical_not -command  -index_list  -sourcesubtract_int64matches_string  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and  -logical_or  logical_and -command  -index_list  -targetmatches_stringcanonical_path_string -command  -index_list  -targetstring_substring_int -command  -index_list  -targetstring_index_of_string  add_int64matches_string  lists_range -command  list_size  -source greater_int64canonical_path_string -command  -index_list  -sourcematches_string  logical_and  -source equals  logical_not -command  -index_list  -sourcesubtract_int64matches_string  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_andcanonical_path_string -command  -index_list  -targetstring_substring_int -command  -index_list  -targetstring_index_of_string  add_int64matches_string  lists_range -command  list_size  -source greater_int64canonical_path_string -command  -index_list  -sourcematches_string  logical_and  -source equals  logical_not -command  -index_list  -sourcesubtract_int64matches_string  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and  -logical_or  logical_and  -logical_or  logical_and  logical_not  lists_range -command  list_size -k greater_int64  -target -command  -index_list -k in_list -command  -index_list -kstarts_with_string  -logical_or -command  -index_list -kmatches_string  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  -logical_or  logical_and  -logical_or -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and -: - - - -: - - -  -: - - - -   2 -Jrule.ShellCommandJrule.ShellCommand2 -Jrule.ShellRedirectJrule.ShellRedirect -2 -Jrule.ShellRedirect !"#$%&'()*Jrule.ShellCommand+,-.Jrule.ShellCommand -/2 -23456789:;<=>?@Jrule.ShellCommandABCDJrule.ShellCommand -E2 -HIJKLMNOPQRSTUVJrule.ShellCommandWXY -[2 -\Jrule.ShellCommand -]2 -^abcdeJrule.ShellCommand -f2 -ghijklmnoJrule.ShellCommand -p2 -qrstuvw -y2 -z}~Jrule.ShellCommand 2 -Jrule.ShellCommandJrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommandJrule.ShellCommandJrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommandJrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommandJrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -"s2s_&&_2_||_=29_==_* - " -event -event_type2 command.exec 2_&&_622_==_* - " -event source_type -2otel? 2;_==_ -* -  " -event -event_type 2command.resultqJq -command" -shell_commands@result"*520@not_strictly_false2!_" -@result2p2p_||_" -@resultp2p_||_2_||_ U2 _||_)J -redirect* - " -command redirects@result""*2%2.@not_strictly_false$2!_ #" -@result2'2_||_ &" -@result!2_&&_D2@@in* -" - -redirectop: - 2write - 2append2 -22.canonical_path* -" - -redirecttargetmatches 2(?i)^((~|\$HOME|\$\{HOME\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\.ssh/authorized_keys2?|(\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\.ssh/authorized_keys2?|(\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$: (" -@resultT2_&&_>2_&&_K,2G -+* - *" -commandnamematches!-2(?i)^(tee|truncate|rm|sed)$=J -arg/* - ." -commandargv@result"6*292.@not_strictly_false82!_ 7" -@result2;2_||_ :" -@result42 -22canonical_path 3" -argmatches52(?i)^((~|\$HOME|\$\{HOME\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\.ssh/authorized_keys2?|(\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\.ssh/authorized_keys2?|(\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$: <" -@resultS2_||_C?2?!_9B25 -A* - @" -commandnamematchesC 2 (?i)^sed$RJ -argE* - D" -commandargv@result"K*2N2.@not_strictly_falseM2!_ L" -@result2VP2R_||_ O" -@result;I27 - H" -argmatches!J2(?i)^(-i|--in-place)(=.*)?$: Q" -@result -2 -_&&_~X2z -W* - V" -commandnamematchesTYP2N(?i)^(set-content|add-content|clear-content|out-file|remove-item|ri|new-item)$J -i8[24 lists.range%^2! -]* - \" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_k2_&&_b2_>_a" -ici2 -Dd2@canonical_path.g2*_[_]f* - e" -commandargvh" -imatchesj2(?i)^((~|\$HOME|\$\{HOME\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\.ssh/authorized_keys2?|(\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\.ssh/authorized_keys2?|(\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$2_||_w2_||_m2_==_l" -inzu2v -Aq2=_[_]p* - o" -commandargvs2_-_r" -itmatches(v$2"(?i)^-(path|literalpath|filepath)$J -jy2 lists.rangez" -i@result"**2%@not_strictly_false" -@result22_&&_" -@result2_||_~2_==_}" -j2 -22-_[_]* -" -commandargv" -jmatchesVQ2O(?i)^-(force|nonewline|passthru|append|noclobber|recurse|whatif|confirm)(:.*)?$:" -@result:" -@resultZ2Z_||_2_&&_2_&&_]2X -* -" -commandnamematches0+2)(?i)^(cp|mv|install|copy-item|move-item)$=28_>_(2# -* -" -commandargvsizeJ -i<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_2_&&_2_>_" -i2 -I2Dcanonical_path22-_[_]* -" -commandargv" -imatches2(?i)^((~|\$HOME|\$\{HOME\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\.ssh/authorized_keys2?|(\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\.ssh/authorized_keys2?|(\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$2_||_2_||_H2C -* -" -commandnamematches2(?i)^(mv|move-item)$2_&&_2_&&_F2A -* -" -commandnamematches2(?i)^(cp|install)$2!_J -arg* -" -commandargv@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_||_2_||_C2>@in -" -arg+:& - 2-t -2--target-directory924 - -" -arg -startsWith2--target-directory=.2) - -" -argmatches 2 ^-[^-]*t.+$:" -@result2_||_T2O_==_" -i=28_-_(2# -* -" -commandargvsizeJ -argr2m -* -" -commandargvslice2_+_" -i(2# -* -" -commandargvsize@result"**2%@not_strictly_false" -@result22_&&_" -@result|2w - -" -argmatches`[2Y(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$:" -@result2_&&_C2> -* -" -commandnamematches2(?i)^copy-item$2_||_r2m -G2B_[_]* -" -commandargv2_-_" -imatches2(?i)^-destination$2_&&_2!_J -arg* -" -commandargv@result"*520@not_strictly_false2!_" -@result2R2M_||_" -@result520 - -" -argmatches2(?i)^-destination$:" -@result2_||_T2O_==_" -i=28_-_(2# -* -" -commandargvsizeJ -argr2m -* -" -commandargvslice2_+_" -i(2# -* -" -commandargvsize@result"**2%@not_strictly_false" -@result2~2y_&&_" -@resulta2\ - -" -argmatchesE@2>(?i)^-(force|recurse|container|passthru|whatif|confirm)(:.*)?$:" -@result:" -@resultD2D_&&_2_&&_I2D -* -" -commandnamematches2(?i)^(cp|mv|install)$=28_>_(2# -* -" -commandargvsizeB2B_||_J -i<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_2_&&_2_>_" -i 2 _||_2_&&_2 -I2Dcanonical_path22-_[_]* -" -commandargv" -imatches2(?i)^((~|\$HOME|\$\{HOME\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))|(\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+))/\.ssh$J -source<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_2_&&_"2_>_ " -source2} -N2Icanonical_path722_[_]* -" -commandargv " -sourcematches"2(?i)(^|/)authorized_keys2?$2_||_#2_==_ " -source2!_2 -L2G_[_]* -" -commandargv"2_-_ " -sourcematches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" -@result2_&&_2 -I2Dcanonical_path22-_[_]* -" -commandargv" -imatchesE@2>(?i)^(\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh$J -source<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_2_&&_"2_>_ " -source2 -N2Icanonical_path722_[_]* -" -commandargv " -sourcematches/*2((?i)(^|/)administrators_authorized_keys$2_||_#2_==_ " -source2!_2 -L2G_[_]* -" -commandargv"2_-_ " -sourcematches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" -@result 2 _||_2_&&_}2x -G2B_[_]* -" -commandargv2_-_" -imatches$2(?i)^(-t|--target-directory)$2!_J -k<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_2_>_" -k" -i2_||_2_||_k2f@in22-_[_]* -" -commandargv" -k+:& - 2-t -2--target-directorya2\ -22-_[_]* -" -commandargv" -k -startsWith2--target-directory=V2Q -22-_[_]* -" -commandargv" -kmatches 2 ^-[^-]*t.+$:" -@result2_&&_2_&&_2_>_" -i2!_J -arg* -" -commandargv@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_||_2_||_C2>@in -" -arg+:& - 2-t -2--target-directory924 - -" -arg -startsWith2--target-directory=.2) - -" -argmatches 2 ^-[^-]*t.+$:" -@resultJ -argr2m -* -" -commandargvslice2_+_" -i(2# -* -" -commandargvsize@result"**2%@not_strictly_false" -@result22_&&_" -@result|2w - -" -argmatches`[2Y(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$:" -@result:" -@result&J& -target<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result2%2%_||_" -@result%2%_&&_ 2 _&&_"2_>_ " -target 2 _||_2_&&_f2a -722_[_]* -" -commandargv " -target -startsWith2--target-directory=2_||_2_&&_2 -i2dcanonical_pathR2M -722_[_]* -" -commandargv " -target substringmatches2(?i)^((~|\$HOME|\$\{HOME\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))|(\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+))/\.ssh$J -source<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_2_&&_"2_>_ " -source2} -N2Icanonical_path722_[_]* -" -commandargv " -sourcematches"2(?i)(^|/)authorized_keys2?$2_||_#2_==_ " -source2!_2 -L2G_[_]* -" -commandargv"2_-_ " -sourcematches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" -@result2_&&_2 -i2dcanonical_pathR2M -722_[_]* -" -commandargv " -target substringmatchesE@2>(?i)^(\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh$J -source<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_2_&&_"2_>_ " -source2 -N2Icanonical_path722_[_]* -" -commandargv " -sourcematches/*2((?i)(^|/)administrators_authorized_keys$2_||_#2_==_ " -source2!_2 -L2G_[_]* -" -commandargv"2_-_ " -sourcematches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" -@result2_&&_[2V -722_[_]* -" -commandargv " -targetmatches 2 ^-[^-]*t.+$2_||_2_&&_2 -2canonical_path2 -722_[_]* -" -commandargv " -target substringf2a_+_Q2L -722_[_]* -" -commandargv " -targetindexOf2tmatches2(?i)^((~|\$HOME|\$\{HOME\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))|(\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+))/\.ssh$J -source<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_2_&&_"2_>_ " -source2} -N2Icanonical_path722_[_]* -" -commandargv " -sourcematches"2(?i)(^|/)authorized_keys2?$2_||_#2_==_ " -source2!_2 -L2G_[_]* -" -commandargv"2_-_ " -sourcematches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" -@result2_&&_2 -2canonical_path2 -722_[_]* -" -commandargv " -target substringf2a_+_Q2L -722_[_]* -" -commandargv " -targetindexOf2tmatchesE@2>(?i)^(\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh$J -source<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_2_&&_"2_>_ " -source2 -N2Icanonical_path722_[_]* -" -commandargv " -sourcematches/*2((?i)(^|/)administrators_authorized_keys$2_||_#2_==_ " -source2!_2 -L2G_[_]* -" -commandargv"2_-_ " -sourcematches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" -@result2!_J -k<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_#2_>_" -k " -target2_||_2_||_k2f@in22-_[_]* -" -commandargv" -k+:& - 2-t -2--target-directorya2\ -22-_[_]* -" -commandargv" -k -startsWith2--target-directory=V2Q -22-_[_]* -" -commandargv" -kmatches 2 ^-[^-]*t.+$:" -@result:" -@result:" -@result*4Ynumbat:cel-env-v1:sha256:0378d920be6102465633215dcf6be9adb8bc87fda5a3148c8f3254f21c3c1b73q - -  !""""#$$%&&&&&&''((()))*****++++,------....///11224444566788999999:<<==????@AABCCDDDDDEEEFFFFFGGGG"""""("-":"=" G" -L" X" [" D"$"q""""""""""""" "!"""#"$"%"&"'"(")"*"+","-"."/"2"3"4"5"6"7"8"9":";"<"=">"?"@"A"B"C"D"E"H"I"J"K"L"M"N"O"P"Q"R"S"T"U"V"W"X"Y"[ "\ "] "^ "a -"b -"c -"d -"e -"f -"g -"h -"i -"j -"k -"l "m "n "o "p "q "r "s "t "u "v "w "y "z "} "~ " """"""" " " " " " " " " " " " " " " " " " " """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " " " " " " " " " " " " "!" "!"!"!"!"!"!"!"!"!"!"!"!"!"!" " " " " " " " ""#"#"#"#"#"#"#"#"#"#"$"$"$"$"$"$"$"$"$"$"$"$"%"%"%"%"%"%"%"%"%"%"%"%"%"%"$"$"$"$"$"$"$"$"#""""'"'"'"'"'"'"'"'"'"'"'"'"'"("("("("("("("("("("("("(")")")")"(")")")")")")")"("'"'"'"'"'"'"'"'"'"*"*"*"*"*"*"*"*"*"*"*"+"+"+"+"+"+"+"+"*"*"*"*"*"*"*"*"*",",",",",",",",",",",",",",",",",",","+"*"&"""""""""."."."."."."."."."."."/"/"/"/"/"/"/"0"0"0"0"1"1"1"1"2"2"2"2"2"2"2"2"2"2"2"3"3"3"3"3"3"3"3"3"3"3"3"3"2"1"1"1"1"1"1"1"1"1"4"4"4"4"5"5"5"5"5"6"6"6"6"6"6"6"6"6"6"6"6"7"7"6"7"7"7"7"7"7"7"7"7"7"7"7"7"7"6"6"6"6"6"6"6"6"5"4"/"9"9"9"9"9"9":":":":":":":":":":":":":":":":"<"<"<"<"<"<"<"="="="="="="="<"="=">">">">">">">">">">">"="<"<"<"<"<"<"<"<"<"?"?"?"?"?"?"?"@"@"@"@"@"@"@"@"@"A"A"A"A"A"A"A"A"A"B"B"B"B"B"A"B"B"B"B"B"B"B"B"C"C"C"C"B"B"A"A"A"A"A"A"A"A"@"?"9"9"."D"D"D"D"D"D"E"E"E"E"E"E"E"E"E"E"E"E"E"E"F"F"E"F"F"F"F"F"F"F"E"D"D"D"D"D"D"D"D"D"."."."."."."."."-"""""""""""n \ No newline at end of file diff --git a/rules/internal/checked/7f5aef9eb4a0b5fe04a85b8dcad0f093f09d1c7fe04c77e58f5e115d356f9ccb.pb b/rules/internal/checked/7f5aef9eb4a0b5fe04a85b8dcad0f093f09d1c7fe04c77e58f5e115d356f9ccb.pb deleted file mode 100644 index 94a120d..0000000 --- a/rules/internal/checked/7f5aef9eb4a0b5fe04a85b8dcad0f093f09d1c7fe04c77e58f5e115d356f9ccb.pb +++ /dev/null @@ -1,899 +0,0 @@ -  -event equals  -event equals   -logical_or -canonical_path_string   -event matches_string  logical_and  -event equals  -event equals  -event equals  logical_and  -logical_or -shell_commands ! -command#matches_string%  logical_not & -command * -arg+matches_string - -arg.matches_string0  -logical_or 1 -arg2matches_string4  -logical_or 5 -arg 6 in_list:  -logical_or < -@result=  logical_not>not_strictly_false ? -@result@  -logical_or A -@resultC  logical_andD  logical_not E -command I -argJmatches_string L -argMmatches_stringO  -logical_or Q -@resultR  logical_notSnot_strictly_false T -@resultU  -logical_or V -@result X -commandZ  -list_slice \ -command^  list_size a -argbmatches_string d -arg e in_listi  -logical_or k -@resultlnot_strictly_false m -@resultn  logical_and o -@resultq  logical_ands  lists_range t -commandv  list_sizey -iz greater_int64 | -command~  -index_list -istarts_with_stringcanonical_path_string -command  -index_list -istring_substring_intmatches_string  logical_and -command  -index_list -imatches_stringcanonical_path_string -command  -index_list -istring_substring_int -command  -index_list -istring_index_of_string  add_int64matches_string  logical_and  -logical_or -command  -index_list -i equals -command  -index_list -imatches_string  -logical_or -i  add_int64  -less_int64 -command  list_size  logical_andcanonical_path_string -command  -index_list -i  add_int64matches_string  logical_and  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  -logical_or  logical_and -command - -redirect in_listcanonical_path_string - -redirectmatches_string  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  -logical_or  lists_range -command  list_size -i greater_int64canonical_path_string -command  -index_list -imatches_string  logical_and -commandmatches_string -commandmatches_string -command - -argmatches_string -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and  -logical_or -commandmatches_string  -logical_or -commandmatches_string  logical_not -command - -arg in_list - -argstarts_with_string  -logical_or - -argmatches_string  -logical_or -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and -i equals -command  list_sizesubtract_int64 -command  -index_list -isubtract_int64matches_string  -logical_or -command  -list_slice -i  add_int64 -command  list_size - -argmatches_string -@resultnot_strictly_false -@result  logical_and -@result  -logical_or  logical_and  -logical_or -commandmatches_string -i equals -command  -index_list -isubtract_int64matches_string  -logical_or  lists_range -i -j equals -command  -index_list -jmatches_string  -logical_or -@resultnot_strictly_false -@result  logical_and -@result  -logical_or  logical_and  -logical_or -commandmatches_string -command  -index_list -isubtract_int64matches_string -i greater_int64  lists_range -command  list_size -jless_equals_int64 -i -command  -index_list -jmatches_string  -logical_or -@resultnot_strictly_false -@result  logical_and -@result  logical_and  -logical_or  logical_and  -logical_or -commandmatches_string -i equals -command  -index_list -isubtract_int64matches_string  -logical_or -i equals -command  list_sizesubtract_int64  -logical_or  lists_range -i -j equals -command  -index_list -jmatches_string  -logical_or -@resultnot_strictly_false -@result  logical_and -@result  -logical_or  logical_and  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  -logical_or -commandmatches_string  lists_range -command  list_size -i greater_int64canonical_path_string -command  -index_list -i equals  logical_and -command  -index_list -isubtract_int64matches_string  logical_not  lists_range -command  list_size -k greater_int64 -i -command  -index_list -k in_list -command  -index_list -kstarts_with_string  -logical_or -command  -index_list -kmatches_string  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and  lists_range -command  list_size -j greater_int64 -j  -not_equals -i  logical_and  logical_not -command  -index_list -jstarts_with_string  logical_and -j equals  logical_not -command  -index_list -jsubtract_int64matches_string  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and -i greater_int64  logical_not -command - -arg in_list - -argstarts_with_string  -logical_or - -argmatches_string  -logical_or -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and -command  -list_slice -i  add_int64 -command  list_size - -argmatches_string -@resultnot_strictly_false -@result  logical_and -@result  logical_and  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and  lists_range -command  list_size  -target greater_int64 -command  -index_list  -targetstarts_with_stringcanonical_path_string -command  -index_list  -targetstring_substring_int equals  logical_and -command  -index_list  -targetmatches_stringcanonical_path_string -command  -index_list  -targetstring_substring_int -command  -index_list  -targetstring_index_of_string  add_int64 equals  logical_and  -logical_or  logical_and  logical_not  lists_range -command  list_size -k greater_int64  -target -command  -index_list -k in_list -command  -index_list -kstarts_with_string  -logical_or -command  -index_list -kmatches_string  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  lists_range -command  list_size -i greater_int64  logical_not -command  -index_list -istarts_with_string  logical_and -i equals  logical_not -command  -index_list -isubtract_int64matches_string  -logical_or  logical_and -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and  -logical_or  -logical_or -@result  logical_notnot_strictly_false -@result  -logical_or -@result  logical_and  -logical_or -: - - - -: - - -  -  -: - -  -  -: - - - -: - - - -: - - -2 -Jrule.ShellCommand!Jrule.ShellCommand"#$%&Jrule.ShellCommand -'2 -*+,-./0123456 -72 -89:;<=>?@ABCDEJrule.ShellCommand -F2 -IJKLMNOPQRSTUVWXJrule.ShellCommand -Y2 - -Z2 -[\Jrule.ShellCommand -]2 -^abcde -f2 -ghijklmnopq -s2 -tJrule.ShellCommand -u2 -vyz{|Jrule.ShellCommand -}2 -~Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand2 -Jrule.ShellRedirectJrule.ShellRedirect 2 -Jrule.ShellRedirect 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommandJrule.ShellCommandJrule.ShellCommand 2 -Jrule.ShellCommandJrule.ShellCommandJrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommandJrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommandJrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommandJrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 - 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -Jrule.ShellCommand 2 -"`2`_||_2_&&_ 2_||_;27_==_* - " -event -event_type 2 -file.write<28_==_* - " -event -event_type 2 file.deleteo 2k -2 -2.canonical_path * -  " -event file_pathmatches,(2&^/etc/sudoers$|^/etc/sudoers\.d/[^/]+$^2^_&&_2_||_=29_==_* - " -event -event_type2 command.exec2_&&_622_==_* - " -event source_type -2otel?2;_==_* - " -event -event_type2command.result\J\ -command" -shell_commands@result"*520@not_strictly_false2!_" -@result2[2[_||_" -@result[2[_||_2_||_2_&&_C2_&&_<#28 -"* - !" -commandnamematches$2 (?i)^visudo$%2!_BJ -arg'* - &" -commandargv@result";*2>2.@not_strictly_false=2!_ <" -@result2@2_||_ ?" -@result42_||_02_||_K+2G - *" -argmatches1,-2+^(--check|--help|--version|--export)(=.*)?$4.20 - -" -argmatches/2^-[qs]*c[qs]*(f.*)?$g:2c_||_+22' - 1" -argmatches3 2 ^-[qs]*x.*$.62*@in 5" -arg7: -82-h -92-V: A" -@result2_||_q2_&&_D2!_WJ -argF* - E" -commandargv@result"P*2S2.@not_strictly_falseR2!_ Q" -@result2U2~_||_ T" -@resultgO2c_||_.J2* - I" -argmatchesK2^--file(=.*)?$+M2' - L" -argmatchesN 2 ^-[qs]*f.*$: V" -@resultpJ -argUZ2Q -Y* - X" -commandargvslice[%^2! -]* - \" -commandargvsize@result"j*(l2$@not_strictly_false k" -@result2n2_&&_ m" -@resultoi2k_||_(b2$ - a" -argmatchesc -2^-[qs]+$9e25@in d" -arg#f: - g 2--quiet -h -2--strict: o" -@result -J - -i8s24 lists.range%v2! -u* - t" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_z2_>_y" -i{2_||_2_||_2_&&_Q2L -.~2*_[_]}* - |" -commandargv" -i -startsWith 2--file=2 -d2_canonical_pathM2H -22-_[_]* -" -commandargv" -i substringmatches!2^/etc/sudoers(\.d/[^/]+)?$2_&&_V2Q -22-_[_]* -" -commandargv" -imatches 2 ^-[qs]*f.+$2 -2canonical_path2 -22-_[_]* -" -commandargv" -i substringa2\_+_L2G -22-_[_]* -" -commandargv" -iindexOf2fmatches!2^/etc/sudoers(\.d/[^/]+)?$2_&&_2_&&_2_||_N2I_==_22-_[_]* -" -commandargv" -i 2--fileT2O -22-_[_]* -" -commandargv" -imatches 2 ^-[qs]*f$S2N_<_2_+_" -i(2# -* -" -commandargvsize2 -^2Ycanonical_pathG2B_[_]* -" -commandargv2_+_" -imatches!2^/etc/sudoers(\.d/[^/]+)?$:" -@resultJ -redirect * -" -command redirects@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_J2E@in* -" - -redirectop": - 2write - 2appendl2g -520canonical_path* -" - -redirecttargetmatches% 2(?i)^/etc/sudoers(\.d/[^/]+)?$:" -@resultE2D_||_J -i<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_2_&&_2_>_" -i2{ -I2Dcanonical_path22-_[_]* -" -commandargv" -imatches% 2(?i)^/etc/sudoers(\.d/[^/]+)?$2_||_ 2 _||_2_||_K2F -* -" -commandnamematches2(?i)^(rm|truncate|tee)$2_&&_=28 -* -" -commandnamematches 2 (?i)^sed$J -arg* -" -commandargv@result"*520@not_strictly_false2!_" -@result2[2V_||_" -@result>29 - -" -argmatches"2(?i)^(-i|--in-place)(=.*)?$:" -@result 2 _||_<27 -* -" -commandnamematches -2(?i)^mv$2_&&_2_&&_F2A -* -" -commandnamematches2(?i)^(cp|install)$2!_J -arg* -" -commandargv@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_||_2_||_C2>@in -" -arg+:& - 2-t -2--target-directory924 - -" -arg -startsWith2--target-directory=.2) - -" -argmatches 2 ^-[^-]*t.+$:" -@result2_||_2_||_T2O_==_" -i=28_-_(2# -* -" -commandargvsize}2x -G2B_[_]* -" -commandargv2_-_" -imatches$2(?i)^(-t|--target-directory)$J -argr2m -* -" -commandargvslice2_+_" -i(2# -* -" -commandargvsize@result"**2%@not_strictly_false" -@result22_&&_" -@result|2w - -" -argmatches`[2Y(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$:" -@result2_||_ -2 -_||_2_&&_2} -* -" -commandnamematchesUP2N(?i)^(set-content|add-content|clear-content|out-file|remove-item|ri|new-item)$2_||_2_||_2_==_" -i2} -G2B_[_]* -" -commandargv2_-_" -imatches)$2"(?i)^-(path|literalpath|filepath)$J -j2 lists.range" -i@result"**2%@not_strictly_false" -@result22_&&_" -@result2_||_2_==_" -j2 -22-_[_]* -" -commandargv" -jmatchesVQ2O(?i)^-(force|nonewline|passthru|append|noclobber|recurse|whatif|confirm)(:.*)?$:" -@result2_&&_C2> -* -" -commandnamematches2(?i)^copy-item$2_||_r2m -G2B_[_]* -" -commandargv2_-_" -imatches2(?i)^-destination$2_&&_2_>_" -iJ -j<27 lists.range(2# -* -" -commandargvsize@result"**2%@not_strictly_false" -@result22_&&_" -@result2_||_2_<=_" -j" -i2 -22-_[_]* -" -commandargv" -jmatchesE@2>(?i)^-(force|recurse|container|passthru|whatif|confirm)(:.*)?$:" -@result2_&&_C2> -* -" -commandnamematches2(?i)^move-item$2_||_2_||_2_==_" -i2 -G2B_[_]* -" -commandargv2_-_" -imatches,'2%(?i)^-(path|literalpath|destination)$2_||_T2O_==_" -i=28_-_(2# -* -" -commandargvsizeJ -j2 lists.range" -i@result"**2%@not_strictly_false" -@result22_&&_" -@result2_||_2_==_" -j2 -22-_[_]* -" -commandargv" -jmatchesE@2>(?i)^-(force|recurse|container|passthru|whatif|confirm)(:.*)?$:" -@result:" -@result%2%_||_2_&&_I2D -* -" -commandnamematches2(?i)^(cp|mv|install)$J -i<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_2_&&_2_>_" -im2h_==_I2Dcanonical_path22-_[_]* -" -commandargv" -i2/etc/sudoers.d2_||_ -2 -_&&_2_&&_}2x -G2B_[_]* -" -commandargv2_-_" -imatches$2(?i)^(-t|--target-directory)$2!_J -k<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_2_>_" -k" -i2_||_2_||_k2f@in22-_[_]* -" -commandargv" -k+:& - 2-t -2--target-directorya2\ -22-_[_]* -" -commandargv" -k -startsWith2--target-directory=V2Q -22-_[_]* -" -commandargv" -kmatches 2 ^-[^-]*t.+$:" -@resultJ -j<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_K2F_&&_2_>_" -j2_!=_" -j" -i2_&&_Z2U!_O2J -22-_[_]* -" -commandargv" -j -startsWith2-2_||_2_==_" -j2!_2 -G2B_[_]* -" -commandargv2_-_" -jmatches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" -@result2_&&_2_&&_2_>_" -i2!_J -arg* -" -commandargv@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_||_2_||_C2>@in -" -arg+:& - 2-t -2--target-directory924 - -" -arg -startsWith2--target-directory=.2) - -" -argmatches 2 ^-[^-]*t.+$:" -@resultJ -argr2m -* -" -commandargvslice2_+_" -i(2# -* -" -commandargvsize@result"**2%@not_strictly_false" -@result22_&&_" -@result|2w - -" -argmatches`[2Y(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$:" -@result:" -@result2_&&_ J -target<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result2 -2 -_||_" -@result 2 _&&_2_&&_"2_>_ " -target2_||_2_&&_f2a -722_[_]* -" -commandargv " -target -startsWith2--target-directory=2_==_i2dcanonical_pathR2M -722_[_]* -" -commandargv " -target substring2/etc/sudoers.d2_&&_[2V -722_[_]* -" -commandargv " -targetmatches 2 ^-[^-]*t.+$2_==_2canonical_path2 -722_[_]* -" -commandargv " -target substringf2a_+_Q2L -722_[_]* -" -commandargv " -targetindexOf2t2/etc/sudoers.d2!_J -k<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_#2_>_" -k " -target2_||_2_||_k2f@in22-_[_]* -" -commandargv" -k+:& - 2-t -2--target-directorya2\ -22-_[_]* -" -commandargv" -k -startsWith2--target-directory=V2Q -22-_[_]* -" -commandargv" -kmatches 2 ^-[^-]*t.+$:" -@result:" -@resultJ -i<27 lists.range(2# -* -" -commandargvsize@result"*520@not_strictly_false2!_" -@result22_||_" -@result2_&&_2_&&_2_>_" -iZ2U!_O2J -22-_[_]* -" -commandargv" -i -startsWith2-2_||_2_==_" -i2!_2 -G2B_[_]* -" -commandargv2_-_" -imatches]X2V(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$:" -@result:" -@result*2Ynumbat:cel-env-v1:sha256:7f5aef9eb4a0b5fe04a85b8dcad0f093f09d1c7fe04c77e58f5e115d356f9ccbO - -  !!!"""####$$%%%&&&&'((((())))*++++++,,,,--...///000111222223444444"" -""")".":"=" &" -_" `" e" x"y"L""""""""""""""""!"""#"$"%"&"'"*"+","-"."/"0"1"2"3"4"5"6"7"8"9":";"<"=">"?"@"A"B"C"D"E"F"I"J"K"L"M"N"O"P"Q"R"S"T"U"V"W"X"Y"Z"["\"]"^"a"b"c"d"e"f"g"h"i"j"k"l"m"n"o"p"q"s"t"u"v"y"z"{"|"}"~"""""""""""""" " " " " " " -" -" -" -" -" -" -" -" -" -" -" -" -" -" -" -" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " -"""""""""""" " " " " " " " """""" " " " " " " " " " """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " "!"!"!"!"!"!"!"!"!"""""""""""""""!"""""""""""""""""#"#"#"#"#"#"#"#"#"$"$"$"$"$"$"$"$"$"$"$"$"$"$"%"%"%"%"%"%"$"#"#"#"#"#"#"#"#"#"#"%"%"%"%"&"&"&"&"&"&"&"&"&"&"&"&"&"&"&"&"&"&"&"&"'"'"'"'"'"'"'"&"&"%"%"%"%"%"%"%"%"%"("("("("("("("("("("(")")")")")")")")"("("("("("("("("("*"*"*"*"*"*"*"*"*"*"*"*"*"*"*"*"*"*"*")"("""!"!"!"!"!"!"!"!"!",",",",",",",",",",","-"-"-"-"-"-"-"-"-"-"-"-"."."."."."."."."."."."/"/"/"/"/"/"/"/"/"/"/".".","/"/"/"0"0"0"0"0"0"0"0"0"0"0"0"0"1"1"1"1"1"1"1"1"1"1"1"1"1"1"0"0"0"0"0"0"0"0"0"/",",",",",",",","2"2"2"2"2"2"2"2"2"2"3"3"3"3"2"3"3"3"3"3"3"3"3"3"3"3"3"3"3"2"2"2"2"2"2"2"2"2"+" """""""""" \ No newline at end of file diff --git a/rules/internal/checked/8008a7f1d1a8caeebe5c0137d010ffaa2fc29993e28f0203fbfd0b270ca296a6.pb b/rules/internal/checked/8008a7f1d1a8caeebe5c0137d010ffaa2fc29993e28f0203fbfd0b270ca296a6.pb new file mode 100644 index 0000000000000000000000000000000000000000..ca8f48e33c876c1d5abd3e7e788417bc8dfad22b GIT binary patch literal 21912 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 G#{UAUH?C>` literal 0 HcmV?d00001 diff --git a/rules/internal/checked/e6634233a808b79f131e95b5a12927ae303b766563660e10f9a6088495bfdec2.pb b/rules/internal/checked/e6634233a808b79f131e95b5a12927ae303b766563660e10f9a6088495bfdec2.pb new file mode 100644 index 0000000000000000000000000000000000000000..8c2634641e9e4749115245e484dde68bbb409c26 GIT binary patch literal 10925 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 PL&c9Q7UDlSK;-`bv+-6Y literal 0 HcmV?d00001 diff --git a/rules/persistence/ssh_authorized_keys_command.yaml b/rules/persistence/ssh_authorized_keys_command.yaml index a72fbab..67fb7a6 100644 --- a/rules/persistence/ssh_authorized_keys_command.yaml +++ b/rules/persistence/ssh_authorized_keys_command.yaml @@ -1,5 +1,5 @@ id: persistence.ssh_authorized_keys_command -version: "1.8" +version: "1.7" enabled: true title: Command targeted SSH authorized_keys description: |- @@ -42,149 +42,10 @@ expr: |- command.name.matches("(?i)^(cp|mv|install|copy-item|move-item)$") && command.argv.size() > 2 && ( - lists.range(command.argv.size()).exists(i, - i > 0 && - canonical_path(command.argv[i]).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\\.ssh/authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\\.ssh/authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$") && - ( - command.name.matches("(?i)^(mv|move-item)$") || - ( - command.name.matches("(?i)^(cp|install)$") && - !command.argv.exists(arg, - arg in ["-t", "--target-directory"] || - arg.startsWith("--target-directory=") || - arg.matches("^-[^-]*t.+$") - ) && - ( - i == command.argv.size() - 1 || - command.argv.slice(i + 1, command.argv.size()).all(arg, - arg.matches("(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$") - ) - ) - ) || - ( - command.name.matches("(?i)^copy-item$") && - ( - command.argv[i - 1].matches("(?i)^-destination$") || - ( - !command.argv.exists(arg, arg.matches("(?i)^-destination$")) && - ( - i == command.argv.size() - 1 || - command.argv.slice(i + 1, command.argv.size()).all(arg, - arg.matches("(?i)^-(force|recurse|container|passthru|whatif|confirm)(:.*)?$") - ) - ) - ) - ) - ) - ) - ) - ) - ) || - ( - command.name.matches("(?i)^(cp|mv|install)$") && - command.argv.size() > 2 && - ( - lists.range(command.argv.size()).exists(i, - i > 0 && - ( - ( - canonical_path(command.argv[i]).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+))/\\.ssh$") && - lists.range(command.argv.size()).exists(source, - source > 0 && - canonical_path(command.argv[source]).matches("(?i)(^|/)authorized_keys2?$") && - (source == 1 || !command.argv[source - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) - ) - ) || - ( - canonical_path(command.argv[i]).matches("(?i)^(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh$") && - lists.range(command.argv.size()).exists(source, - source > 0 && - canonical_path(command.argv[source]).matches("(?i)(^|/)administrators_authorized_keys$") && - (source == 1 || !command.argv[source - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) - ) - ) - ) && - ( - ( - command.argv[i - 1].matches("(?i)^(-t|--target-directory)$") && - !lists.range(command.argv.size()).exists(k, - k > i && - ( - command.argv[k] in ["-t", "--target-directory"] || - command.argv[k].startsWith("--target-directory=") || - command.argv[k].matches("^-[^-]*t.+$") - ) - ) - ) || - ( - i > 1 && - !command.argv.exists(arg, - arg in ["-t", "--target-directory"] || - arg.startsWith("--target-directory=") || - arg.matches("^-[^-]*t.+$") - ) && - command.argv.slice(i + 1, command.argv.size()).all(arg, - arg.matches("(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$") - ) - ) - ) - ) || - ( - lists.range(command.argv.size()).exists(target, - target > 0 && - ( - ( - command.argv[target].startsWith("--target-directory=") && - ( - ( - canonical_path(command.argv[target].substring(19)).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+))/\\.ssh$") && - lists.range(command.argv.size()).exists(source, - source > 0 && - canonical_path(command.argv[source]).matches("(?i)(^|/)authorized_keys2?$") && - (source == 1 || !command.argv[source - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) - ) - ) || - ( - canonical_path(command.argv[target].substring(19)).matches("(?i)^(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh$") && - lists.range(command.argv.size()).exists(source, - source > 0 && - canonical_path(command.argv[source]).matches("(?i)(^|/)administrators_authorized_keys$") && - (source == 1 || !command.argv[source - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) - ) - ) - ) - ) || - ( - command.argv[target].matches("^-[^-]*t.+$") && - ( - ( - canonical_path(command.argv[target].substring(command.argv[target].indexOf("t") + 1)).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+))/\\.ssh$") && - lists.range(command.argv.size()).exists(source, - source > 0 && - canonical_path(command.argv[source]).matches("(?i)(^|/)authorized_keys2?$") && - (source == 1 || !command.argv[source - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) - ) - ) || - ( - canonical_path(command.argv[target].substring(command.argv[target].indexOf("t") + 1)).matches("(?i)^(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh$") && - lists.range(command.argv.size()).exists(source, - source > 0 && - canonical_path(command.argv[source]).matches("(?i)(^|/)administrators_authorized_keys$") && - (source == 1 || !command.argv[source - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) - ) - ) - ) - ) - ) && - !lists.range(command.argv.size()).exists(k, - k > target && - ( - command.argv[k] in ["-t", "--target-directory"] || - command.argv[k].startsWith("--target-directory=") || - command.argv[k].matches("^-[^-]*t.+$") - ) - ) - ) + canonical_path(command.argv[command.argv.size() - 1]).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\\.ssh/authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\\.ssh/authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$") || + lists.range(command.argv.size() - 1).exists(i, + command.argv[i].matches("(?i)^-destination$") && + canonical_path(command.argv[i + 1]).matches("(?i)^((~|\\$HOME|\\$\\{HOME\\}|/(home/[^/]+|Users/[^/]+|root|var/root|private/var/root))/\\.ssh/authorized_keys2?|(\\$env:USERPROFILE|%USERPROFILE%|[A-Z]:/Users/[^/]+)/\\.ssh/authorized_keys2?|(\\$env:ProgramData|%ProgramData%|[A-Z]:/ProgramData)/ssh/administrators_authorized_keys)$") ) ) ) diff --git a/rules/privilege/sudoers_tamper.yaml b/rules/privilege/sudoers_tamper.yaml index dd2062d..9e5996a 100644 --- a/rules/privilege/sudoers_tamper.yaml +++ b/rules/privilege/sudoers_tamper.yaml @@ -1,5 +1,5 @@ id: privilege.sudoers_tamper -version: "1.5" +version: "1.4" enabled: true title: Agent targeted sudoers policy for modification description: |- @@ -17,20 +17,17 @@ expr: |- ( command.name.matches("(?i)^visudo$") && !command.argv.exists(arg, - arg.matches("^(--check|--help|--version|--export)(=.*)?$") || - arg.matches("^-[qs]*c[qs]*(f.*)?$") || - arg.matches("^-[qs]*x.*$") || - arg in ["-h", "-V"] + arg in ["--check", "--help", "--version", "-h", "-V"] || + arg == "--export" || + arg.startsWith("--export=") || + arg.matches("^-[IOPqs]*c") || + arg.matches("^-[IOPqs]*x") ) && ( ( - !command.argv.exists(arg, - arg.matches("^--file(=.*)?$") || - arg.matches("^-[qs]*f.*$") - ) && command.argv.slice(1, command.argv.size()).all(arg, - arg.matches("^-[qs]+$") || - arg in ["--quiet", "--strict"] + arg.matches("^-[IOPqs]+$") || + arg in ["--no-includes", "--owner", "--perms", "--quiet", "--strict"] ) ) || lists.range(command.argv.size()).exists(i, @@ -41,15 +38,23 @@ expr: |- canonical_path(command.argv[i].substring(7)).matches("^/etc/sudoers(\\.d/[^/]+)?$") ) || ( - command.argv[i].matches("^-[qs]*f.+$") && + command.argv[i].matches("^-[IOPqs]*f.+$") && canonical_path(command.argv[i].substring(command.argv[i].indexOf("f") + 1)).matches("^/etc/sudoers(\\.d/[^/]+)?$") ) || ( - (command.argv[i] == "--file" || command.argv[i].matches("^-[qs]*f$")) && + (command.argv[i] == "--file" || command.argv[i].matches("^-[IOPqs]*f$")) && i + 1 < command.argv.size() && canonical_path(command.argv[i + 1]).matches("^/etc/sudoers(\\.d/[^/]+)?$") ) ) + ) || + ( + command.argv.size() > 1 && + canonical_path(command.argv[command.argv.size() - 1]).matches("^/etc/sudoers(\\.d/[^/]+)?$") && + command.argv.slice(1, command.argv.size() - 1).all(arg, + arg.matches("^-[IOPqs]+$") || + arg in ["--no-includes", "--owner", "--perms", "--quiet", "--strict"] + ) ) ) ) || @@ -69,17 +74,9 @@ expr: |- command.name.matches("(?i)^mv$") || ( command.name.matches("(?i)^(cp|install)$") && - !command.argv.exists(arg, - arg in ["-t", "--target-directory"] || - arg.startsWith("--target-directory=") || - arg.matches("^-[^-]*t.+$") - ) && ( i == command.argv.size() - 1 || - command.argv[i - 1].matches("(?i)^(-t|--target-directory)$") || - command.argv.slice(i + 1, command.argv.size()).all(arg, - arg.matches("(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$") - ) + command.argv[i - 1].matches("(?i)^(-t|--target-directory)$") ) ) || ( @@ -119,71 +116,6 @@ expr: |- ) ) ) - ) || - ( - command.name.matches("(?i)^(cp|mv|install)$") && - lists.range(command.argv.size()).exists(i, - i > 0 && - canonical_path(command.argv[i]) == "/etc/sudoers.d" && - ( - ( - command.argv[i - 1].matches("(?i)^(-t|--target-directory)$") && - !lists.range(command.argv.size()).exists(k, - k > i && - ( - command.argv[k] in ["-t", "--target-directory"] || - command.argv[k].startsWith("--target-directory=") || - command.argv[k].matches("^-[^-]*t.+$") - ) - ) && - lists.range(command.argv.size()).exists(j, - j > 0 && - j != i && - !command.argv[j].startsWith("-") && - (j == 1 || !command.argv[j - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) - ) - ) || - ( - i > 1 && - !command.argv.exists(arg, - arg in ["-t", "--target-directory"] || - arg.startsWith("--target-directory=") || - arg.matches("^-[^-]*t.+$") - ) && - command.argv.slice(i + 1, command.argv.size()).all(arg, - arg.matches("(?i)^(-f|--force|-i|--interactive|-n|--no-clobber|-T|--no-target-directory|-v|--verbose)$") - ) - ) - ) - ) || - ( - lists.range(command.argv.size()).exists(target, - target > 0 && - ( - ( - command.argv[target].startsWith("--target-directory=") && - canonical_path(command.argv[target].substring(19)) == "/etc/sudoers.d" - ) || - ( - command.argv[target].matches("^-[^-]*t.+$") && - canonical_path(command.argv[target].substring(command.argv[target].indexOf("t") + 1)) == "/etc/sudoers.d" - ) - ) && - !lists.range(command.argv.size()).exists(k, - k > target && - ( - command.argv[k] in ["-t", "--target-directory"] || - command.argv[k].startsWith("--target-directory=") || - command.argv[k].matches("^-[^-]*t.+$") - ) - ) - ) && - lists.range(command.argv.size()).exists(i, - i > 0 && - !command.argv[i].startsWith("-") && - (i == 1 || !command.argv[i - 1].matches("(?i)^(-S|--suffix|-g|--group|-m|--mode|-o|--owner|--strip-program|--context|--sparse)$")) - ) - ) ) ) ) diff --git a/rules/release_precision_policy_test.go b/rules/release_precision_policy_test.go index 80f800f..545f7ca 100644 --- a/rules/release_precision_policy_test.go +++ b/rules/release_precision_policy_test.go @@ -20,6 +20,7 @@ func TestReleasePrecisionPrivilegePolicy(t *testing.T) { {"visudo validation is not a modification", cmd("visudo -c"), ""}, {"combined visudo validation flags are not a modification", cmd("visudo -cf /etc/sudoers"), ""}, {"clustered attached visudo validation is not a modification", cmd("visudo -qcf/etc/sudoers"), ""}, + {"clustered no-includes validation is not a modification", cmd("visudo -Icf/etc/sudoers"), ""}, {"visudo help is not a modification", cmd("visudo --help"), ""}, {"visudo export is not a modification", cmd("visudo --export=/tmp/sudoers.json"), ""}, {"short visudo export is not a modification", cmd("visudo -f /etc/sudoers -x /tmp/sudoers.json"), ""}, @@ -35,6 +36,9 @@ func TestReleasePrecisionPrivilegePolicy(t *testing.T) { {"visudo clustered file option against active policy", cmd("visudo -qf /etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, {"visudo clustered attached file against active policy", cmd("visudo -qf/etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, {"visudo clustered edit options target default policy", cmd("visudo -qs"), "privilege.sudoers_tamper"}, + {"visudo no-includes option targets default policy", cmd("visudo -I"), "privilege.sudoers_tamper"}, + {"visudo clustered no-includes and file option", cmd("visudo -If/etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, + {"visudo positional active policy", cmd("visudo /etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, {"visudo combined file through proc task-root alias", cmd("visudo --file=/proc/4321/task/8765/root/etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, {"visudo combined file through traversal alias", cmd("visudo --file=/etc/numbat/../sudoers"), "privilege.sudoers_tamper"}, {"later visudo validation cannot suppress active policy edit", cmd("visudo --file=/etc/sudoers.d/agent; visudo -c"), "privilege.sudoers_tamper"}, @@ -42,16 +46,7 @@ func TestReleasePrecisionPrivilegePolicy(t *testing.T) { {"quoted sudoers fixture is not a modification", cmd(`echo "tee /etc/sudoers.d/agent"`), ""}, {"quoted sudoers example cannot suppress a real mutation", cmd(`printf policy > /etc/sudoers.d/agent; echo "printf example > /etc/sudoers"`), "privilege.sudoers_tamper"}, {"sudoers copy target", cmd("cp /tmp/policy /etc/sudoers.d/agent"), "privilege.sudoers_tamper"}, - {"sudoers copy target before trailing option", cmd("cp /tmp/policy /etc/sudoers.d/agent --force"), "privilege.sudoers_tamper"}, - {"sudoers copy target directory", cmd("cp -t /etc/sudoers.d /tmp/agent"), "privilege.sudoers_tamper"}, - {"sudoers copy inline target directory", cmd("cp --target-directory=/proc/self/root/etc/sudoers.d /tmp/agent"), "privilege.sudoers_tamper"}, - {"sudoers copy positional target directory", cmd("cp /tmp/agent /etc/sudoers.d"), "privilege.sudoers_tamper"}, - {"target directory option value is not a copy source", cmd("cp -t /etc/sudoers.d --suffix agent"), ""}, - {"sudoers copy source before trailing option stays quiet", cmd("cp /etc/sudoers /tmp/policy --force"), ""}, - {"sudoers copy source with target directory stays quiet", cmd("cp -t /tmp /etc/sudoers"), ""}, - {"later target-directory option overrides apparent sudoers destination", cmd("cp /tmp/policy /etc/sudoers --target-directory /tmp"), ""}, - {"later target-directory option overrides protected target directory", cmd("cp -t /etc/sudoers.d -t /tmp /tmp/agent"), ""}, - {"last protected target-directory option wins", cmd("cp -t /tmp -t /etc/sudoers.d /tmp/agent"), "privilege.sudoers_tamper"}, + {"cp suffix beginning with t is not target-directory", cmd("cp -St/etc/sudoers.d /tmp/policy /tmp/dest"), ""}, {"moving active sudoers away is tampering", cmd("mv /etc/sudoers /tmp/sudoers"), "privilege.sudoers_tamper"}, {"moving active sudoers away with PowerShell is tampering", cmd("Move-Item -Path /etc/sudoers -Destination /tmp/sudoers"), "privilege.sudoers_tamper"}, {"sudoers path used as PowerShell value stays quiet", cmd(`Set-Content -Path /tmp/note -Value '/etc/sudoers.d/agent'`), ""}, @@ -130,16 +125,8 @@ func TestReleasePrecisionPersistencePolicy(t *testing.T) { {"authorized keys is the PowerShell copy source stays quiet", cmd(`Copy-Item ~/.ssh/authorized_keys /tmp/keys`), ""}, {"authorized keys is the named PowerShell copy source stays quiet", cmd(`Copy-Item -Path ~/.ssh/authorized_keys /tmp/keys`), ""}, {"authorized keys is the PowerShell copy destination", cmd(`Copy-Item /tmp/keys ~/.ssh/authorized_keys`), "persistence.ssh_authorized_keys_command"}, - {"authorized keys copy target before trailing option", cmd(`cp /tmp/keys ~/.ssh/authorized_keys --force`), "persistence.ssh_authorized_keys_command"}, - {"authorized keys copy into positional directory", cmd(`cp /tmp/authorized_keys ~/.ssh`), "persistence.ssh_authorized_keys_command"}, - {"authorized keys copy into target directory", cmd(`cp -t ~/.ssh /tmp/authorized_keys`), "persistence.ssh_authorized_keys_command"}, - {"later target-directory option overrides ssh destination", cmd(`cp --target-directory=~/.ssh --target-directory=/tmp /tmp/authorized_keys`), ""}, - {"last ssh target-directory option wins", cmd(`cp --target-directory=/tmp --target-directory=~/.ssh /tmp/authorized_keys`), "persistence.ssh_authorized_keys_command"}, - {"unrelated file copied into ssh directory stays quiet", cmd(`cp /tmp/notes ~/.ssh`), ""}, - {"backup suffix is not an authorized keys source", cmd(`cp -t ~/.ssh --backup --suffix authorized_keys /tmp/notes`), ""}, - {"authorized keys copy source before trailing option stays quiet", cmd(`cp ~/.ssh/authorized_keys /tmp/keys --force`), ""}, - {"authorized keys copy source with target directory stays quiet", cmd(`cp -t /tmp ~/.ssh/authorized_keys`), ""}, - {"moving authorized keys away is a mutation", cmd(`mv ~/.ssh/authorized_keys /tmp/keys`), "persistence.ssh_authorized_keys_command"}, + {"authorized keys path used as mv backup suffix stays quiet", cmd(`mv --suffix ~/.ssh/authorized_keys /tmp/source /tmp/dest`), ""}, + {"cp suffix beginning with t is not an SSH target directory", cmd(`cp -St~/.ssh /tmp/authorized_keys /tmp/dest`), ""}, {"Windows drive root traversal reaches administrator authorized keys", write(`C:\..\ProgramData\ssh\administrators_authorized_keys`), "persistence.ssh_authorized_keys"}, {"commandless authorized-keys redirect", cmd("> ~/.ssh/authorized_keys"), "persistence.ssh_authorized_keys_command"}, {"quoted authorized-keys example cannot suppress a real mutation", cmd(`printf key > ~/.ssh/authorized_keys; echo "printf example > ~/.ssh/authorized_keys"`), "persistence.ssh_authorized_keys_command"},