Skip to content

Commit 6eea6e6

Browse files
inqrphlAhmet Oeztuerk
andauthored
powershell: external commands quouting improvements (#361)
Co-authored-by: Ahmet Oeztuerk <Ahmet.Oeztuerk@consol.de>
1 parent e9fca88 commit 6eea6e6

5 files changed

Lines changed: 495 additions & 12 deletions

File tree

packaging/snclient.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ allow arguments = false
349349
; we will allow clients to specify nasty (as defined in nasty characters) characters in arguments.
350350
allow nasty characters = false
351351
352-
; Script root folder - Root path where all scripts are contained (You can not upload/download scripts outside this folder).
352+
; Script root folder - Root path where all scripts are contained. Used in external script wrappers.
353353
script root = ${scripts}
354354
355355
; Load all scripts in a given folder - Load all (${script path}/*) scripts in a given directory and use them as commands.

pkg/snclient/snclient.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,8 +1096,8 @@ func (snc *Agent) restartWatcherCb(restartCb func()) {
10961096
}
10971097

10981098
func fixReturnCodes(output, stderr *string, exitCode *int64, timeout int64, cmd *exec.Cmd, procState *os.ProcessState, err error) {
1099-
log.Tracef("stdout: %s", *output)
1100-
log.Tracef("stderr: %s", *stderr)
1099+
log.Tracef("stdout: \n%s", *output)
1100+
log.Tracef("stderr: \n%s", *stderr)
11011101
log.Tracef("exitCode: %d", *exitCode)
11021102
log.Tracef("timeout: %d", timeout)
11031103
log.Tracef("error: %#v", err)
@@ -1367,9 +1367,9 @@ func (snc *Agent) MakeCmd(ctx context.Context, command string) (*exec.Cmd, error
13671367
case err != nil:
13681368
return nil, err
13691369
case cmd.Args != nil:
1370-
log.Tracef("command object:\n path: %s\n args: %v\n dir: %s\n workingDirectory: %s\n SysProcAttr: %v\n", cmd.Path, cmd.Args, cmd.Dir, workingDirectory, cmd.SysProcAttr)
1370+
log.Tracef("command object:\n path: %s\n args: %v\n dir: %s\n workingDirectory: %s\n SysProcAttr: %#v\n", cmd.Path, cmd.Args, cmd.Dir, workingDirectory, cmd.SysProcAttr)
13711371
default:
1372-
log.Tracef("command object:\n path: %s\n args: (none)\n dir: %s\n workingDirectory: %s\n SysProcAttr: %v\n", cmd.Path, cmd.Dir, workingDirectory, cmd.SysProcAttr)
1372+
log.Tracef("command object:\n path: %s\n args: (none)\n dir: %s\n workingDirectory: %s\n SysProcAttr: %#v\n", cmd.Path, cmd.Dir, workingDirectory, cmd.SysProcAttr)
13731373
}
13741374

13751375
return cmd, err

pkg/snclient/snclient_windows.go

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,8 @@ func (snc *Agent) makeCmd(ctx context.Context, command string) (*exec.Cmd, error
262262
cmd := execCommandContext(ctx, "powershell", env)
263263
cmd.SysProcAttr.CmdLine = fmt.Sprintf(`%s -command %s; exit($LASTEXITCODE)`, POWERSHELL, command)
264264

265+
log.Tracef("cmd.SysProcAttr.CmdLine: %s", cmd.SysProcAttr.CmdLine)
266+
265267
return cmd, nil
266268

267269
// command does not exist
@@ -283,17 +285,34 @@ func (snc *Agent) makeCmd(ctx context.Context, command string) (*exec.Cmd, error
283285
strings.Join(cmdArgs, " "),
284286
)
285287

288+
log.Tracef("cmd.SysProcAttr.CmdLine: %s", cmd.SysProcAttr.CmdLine)
289+
286290
return cmd, nil
287291

288292
// powershell files
289293
case isPsFile(cmdName):
290-
for i, ca := range cmdArgs {
291-
if strings.ContainsAny(ca, " \t") {
292-
cmdArgs[i] = `'` + ca + `'`
293-
}
294+
// parse the command one more time, this time adding the shelltoken.KeepQuoutes option
295+
cmdName, cmdArgs, _, err = snc.shellParse(command, shelltoken.SplitKeepQuotes)
296+
if err != nil {
297+
return nil, err
298+
}
299+
300+
cmdArgsModified := make([]string, 0, len(cmdArgs))
301+
for _, cmdArg := range cmdArgs {
302+
// parsed arguments might include double quoutes.
303+
// cmd.SysProcAttr.CmdLine is set so that the arguments are found within double quoutes inside the -Command parameter
304+
// to include a double quoute here, you have to add three double quoutes.
305+
// windows has very confusing, runtime-dependent command line argument parsing
306+
// This is done with the assumption that its using GetCommandLineW, and it works for now
307+
cmdArg = strings.ReplaceAll(cmdArg, `"`, `"""`)
308+
309+
cmdArgsModified = append(cmdArgsModified, cmdArg)
294310
}
311+
295312
cmd := execCommandContext(ctx, "powershell", env)
296-
cmd.SysProcAttr.CmdLine = fmt.Sprintf(`%s -Command ". '%s' %s; exit($LASTEXITCODE)"`, POWERSHELL, cmdName, strings.Join(cmdArgs, " "))
313+
cmd.SysProcAttr.CmdLine = fmt.Sprintf(`%s -Command ". '%s' %s ; exit($LASTEXITCODE)"`, POWERSHELL, cmdName, strings.Join(cmdArgsModified, " "))
314+
315+
log.Tracef("cmd.SysProcAttr.CmdLine: %s", cmd.SysProcAttr.CmdLine)
297316

298317
return cmd, nil
299318

@@ -312,12 +331,29 @@ func (snc *Agent) makeCmd(ctx context.Context, command string) (*exec.Cmd, error
312331
shell,
313332
strings.Replace(command, cmdName, syscall.EscapeArg(cmdName), 1))
314333

334+
log.Tracef("cmd.SysProcAttr.CmdLine: %s", cmd.SysProcAttr.CmdLine)
335+
315336
return cmd, nil
316337
}
317338
}
318339

319-
func (snc *Agent) shellParse(command string) (cmdName string, args []string, hasShellCode bool, err error) {
320-
args, err = shelltoken.SplitQuotes(command, shelltoken.Whitespace, shelltoken.SplitKeepBackslashes|shelltoken.SplitContinueOnShellCharacters)
340+
func (snc *Agent) shellParse(command string, additionalOptions ...shelltoken.SplitOption) (cmdName string, args []string, hasShellCode bool, err error) {
341+
options := shelltoken.SplitKeepBackslashes | shelltoken.SplitContinueOnShellCharacters
342+
343+
// shelltoken.SplitKeepQuotes may be passed as an additional Option
344+
// when invoking a script, the script might use $ARG1$ macro
345+
// arg1 here might be a single string composed of many arguments, e.g:
346+
// powershell_detail_arg1 "-option1 option1 -option2 `'option2`' -option3 `"option3`" -option4 `'foo,bar`' -option5 `"baz,xyz`" "
347+
// if sheltoken.SplitKeepQuoutes option is not set, it strips the quotation marks from each option value
348+
// this leads to arguments like foo,bar not being quouted and being left as is
349+
// powershell then thinks its an array due to comma
350+
// if they were quouted, it would think that they are a string that includes comma character
351+
352+
for _, opt := range additionalOptions {
353+
options |= opt
354+
}
355+
356+
args, err = shelltoken.SplitQuotes(command, shelltoken.Whitespace, options)
321357
if err != nil {
322358
tst := &shelltoken.ShellCharactersFoundError{}
323359
if errors.As(err, &tst) {

0 commit comments

Comments
 (0)