How to securely pass string literal parameters to powershell.exe

Viewed 816

When calling a PowerShell script from another environment (e.g. Java) literal string arguments need to be passed as part of the process arguments.

How do I sanitize or escape user input that will be passed as string literal to a PowerShell call?

I couldn't find complete guidance anywhere.

3 Answers

Surround the literal with single quotes

  • replace existing single quotes with double single quotes
  • remove (or fail on) `0 (zero) characters - powershell can handle them internally, but they can't be passed to a child process
  • (depending on what your program does, you might also want to remove input below 32, or even above 127. this has nothing to do with encoding though)

That's all folks.

Arguments: single string vs array

I recommend to pass all arguments as one space-separated string, even if you use a process start API that accepts an array of strings.

If you pass an array of arguments, PowerShell will just join them with single spaces and then separate them again according to its own logic. (This works even if one argument starts with quotes and another one ends with quotes.)

Passing an array of arguments just makes the code ugly, masks what’s actually happening, and might provide a false sense of security.

To test correctness of argument passing for strings

  • try adding multiple spaces in a row: when used correctly, they are preserved exactly
  • try passing values in parentheses:
    • when passed correctly, they are passed as literals
    • otherwise, PowerShell tries to execute the value in parenthesis, e.g. powershell.exe write-host "a b (c)" results in an error: "The term 'c' is not recognized"
    • warning: escaping parenthesis as in `(c`) can seem like a solution, but is actually just masking an error: the argument was not recognized as a string literal.
  • Try adding special characters

Hint: When calling powershell.exe from another shell like cmd.exe or bash, don’t confuse that shell's parsing and PowerShell’s parsing. E.g quotes limit arguments for cmd.exe or bash, but the arguments are passed to PowerShell without quotes. This can lead to some confusion.

So offline someone recommended just putting the string values into environment variables. While correctly and safely encoding the string seems entirely possible, avoiding the problem altogether seems like a preferable solution to me.

In C# that would look like this:

var startInfo = new ProcessStartInfo("powershell.exe");
startInfo.Arguments = "Some-Command -StringArg1 $env:StringArg1 -StringArg2 $env:StringArg2";
startInfo.EnvironmentVariables.Add ("StringArg1", unencodedValue1);
startInfo.EnvironmentVariables.Add ("StringArg2", unencodedValue2);
Process.Start (startInfo); 

if you create named parameters when calling a PowerShell scirpt, you can use them normally as a variable in the script, and you do not need to worry about the order they are in when you create the parameters in the script. Instead of creating a variable name for them, and then create the parameters, inside your script.

For example,

Calling your PowerShell script, .\Named_Parameters_Example_2.ps1 -envname Odyssey -servername HAL

Named_Parameters_Example_2.ps1,

write-host "If this script were really going to do something, it would do it on $servername in the $envname environment"
Related