From cbfadc4259ef850eda23ec1b964a64f141ef0a1c Mon Sep 17 00:00:00 2001 From: Darragh O'Reilly Date: Thu, 10 Jul 2025 09:32:30 +0100 Subject: [PATCH] Allow setting prevent_execve without breaking artifacts A couple of artifacts depend on the execve plugin and won't work if prevent_execve is enabled. This patch makes exceptions for the two specific commands that are needed so execve will run them even if prevent_execve is enabled. This patch also fixes CVE-2025-0914 by propagating the client config to queries. --- actions/vql.go | 1 + vql/common/shell.go | 28 +++++++++++++++++++++------- vql/common/shell_test.go | 22 ++++++++++++++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/actions/vql.go b/actions/vql.go index 111cd0e61..918e5043d 100644 --- a/actions/vql.go +++ b/actions/vql.go @@ -147,6 +147,7 @@ func (self VQLClientAction) StartQuery( builder := services.ScopeBuilder{ Config: &config_proto.Config{ + Client: config_obj.Client, Remappings: config_obj.Remappings, }, // Only provide the client config since we are running in diff --git a/vql/common/shell.go b/vql/common/shell.go index 06fba8a69..d084469d1 100644 --- a/vql/common/shell.go +++ b/vql/common/shell.go @@ -69,13 +69,6 @@ func (self ShellPlugin) Call( return } - // Check the config if we are allowed to execve at all. - config_obj, ok := artifacts.GetConfig(scope) - if ok && config_obj.PreventExecve { - scope.Log("shell: Not allowed to execve by configuration.") - return - } - arg := &ShellPluginArgs{} err = arg_parser.ExtractArgsWithContext(ctx, scope, args, arg) if err != nil { @@ -83,6 +76,13 @@ func (self ShellPlugin) Call( return } + // Check the config if we are allowed to execve at all. + config_obj, ok := artifacts.GetConfig(scope) + if ok && config_obj.PreventExecve && !alwaysAllow(arg) { + scope.Log("shell: Not allowed to execve by configuration.") + return + } + var env *ordereddict.Dict if arg.Env != nil { env = vfilter.RowToDict(ctx, scope, arg.Env.Reduce(ctx)) @@ -387,6 +387,20 @@ func defaultPipeReader( return nil } +// alwaysAllow returns true for execve calls required by artifacts. +func alwaysAllow(args *ShellPluginArgs) bool { + if args.Env != nil || args.Cwd != "" || len(args.Argv) < 2 { + return false + } + + switch strings.Join(args.Argv[:2], " ") { + case "systemctl show", "systemctl list-timers": + return true + } + + return false +} + func init() { vql_subsystem.RegisterPlugin(&ShellPlugin{ pipeReader: defaultPipeReader, diff --git a/vql/common/shell_test.go b/vql/common/shell_test.go index 082bd1217..1c689b582 100644 --- a/vql/common/shell_test.go +++ b/vql/common/shell_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/suite" "www.velocidex.com/golang/velociraptor/json" "www.velocidex.com/golang/velociraptor/vtesting/assert" + "www.velocidex.com/golang/vfilter" ) type ShellTestSuite struct { @@ -95,6 +96,27 @@ func (self *ShellTestSuite) TestSplit() { assert.Equal(self.T(), 4, offset) } +func (self *ShellTestSuite) TestAlwaysAllow() { + testCases := []struct { + argv []string + env vfilter.LazyExpr + cwd string + expect bool + }{ + {[]string{"systemctl", "show", "apache"}, nil, "", true}, + {[]string{"systemctl", "list-timers"}, nil, "", true}, + {[]string{"systemctl", "stop", "firewalld"}, nil, "", false}, + {[]string{"ls", "-l"}, nil, "", false}, + {[]string{"systemctl", "show", "apache"}, nil, "/tmp", false}, + {[]string{"systemctl", "show", "apache"}, &vfilter.LazyExprImpl{}, "", false}, + } + + for _, tc := range testCases { + args := &ShellPluginArgs{Argv: tc.argv, Env: tc.env, Cwd: tc.cwd} + assert.Equal(self.T(), tc.expect, alwaysAllow(args), "args: %+v, expected: %v", args, tc.expect) + } +} + func TestExecvePlugin(t *testing.T) { suite.Run(t, &ShellTestSuite{}) }