Command injection (or OS Command Injection) is a type of injection where software that constructs a system command using externally influenced input does not correctly neutralize the input from special elements that can modify the initially intended command.
For example, if the supplied value is:
calc
when typed in a Windows command prompt, the application Calculator is displayed.
However, if the supplied value has been tampered with, and now it is:
calc & echo "test"
when executed, it changes the meaning of the initial intended value.
Now, both the Calculator application and the value test are displayed:
The problem is exacerbated if the compromised process does not follow the principle of least privileges and attacker-controlled commands end up running with special system privileges that increase the amount of damage.
The primary defense is to avoid calling OS commands directly. Built-in library functions are a very good alternative to OS Commands, as they cannot be manipulated to perform tasks other than those it is intended to do.
For example use mkdir()
instead of system("mkdir /dir_name")
.
If there are available libraries or APIs for the language you use, this is the preferred method.
TODO: To enhance.
For examples, see escapeshellarg() or escapeshellcmd() in PHP.
If calling a system command that incorporates user-supplied cannot be avoided, the following two layers of defense should be used within software to prevent attacks:
Parameterization: If available, use structured mechanisms that automatically enforce the separation between data and command. These mechanisms can help provide the relevant quoting and encoding.
Input validation: The values for commands and the relevant arguments should be both validated. There are different degrees of validation for the actual command and its arguments:
- When it comes to the commands used, these must be validated against a list of allowed commands.
- In regards to the arguments used for these commands, they should be validated using the following options:
- Positive or allow list input validation: Where are the arguments allowed explicitly defined.
- Allow list Regular Expression: Where a list of good, allowed characters and the maximum length of the string are defined. Ensure that metacharacters like ones specified in
Note A
and white-spaces are not part of the Regular Expression. For example, the following regular expression only allows lowercase letters and numbers and does not contain metacharacters. The length is also being limited to 3-10 characters:^[a-z0-9]{3,10}$
Note A:
& | ; $ > < ` \ !
On top of primary defenses, parameterizations, and input validation, we also recommend adopting all of these additional defenses to provide defense in depth.
These additional defenses are:
- Applications should run using the lowest privileges that are required to accomplish the necessary tasks.
- If possible, create isolated accounts with limited privileges that are only used for a single task.
In Java, use ProcessBuilder and the command must be separated from its arguments.
Note about the Java's Runtime.exec
method behavior:
There are many sites that will tell you that Java's Runtime.exec
is exactly the same as C
's system function. This is not true. Both allow you to invoke a new program/process.
However, C
's system function passes its arguments to the shell (/bin/sh
) to be parsed, whereas Runtime.exec
tries to split the string into an array of words, then executes the first word in the array with the rest of the words as parameters.
Runtime.exec
does NOT try to invoke the shell at any point and does not support shell metacharacters.
The key difference is that much of the functionality provided by the shell that could be used for mischief (chaining commands using &
, &&
, |
, ||
, etc, redirecting input and output) would simply end up as a parameter being passed to the first command, likely causing a syntax error or being thrown out as an invalid parameter.
Code to test the note above:
String[] specialChars = new String[]{"&", "&&", "|", "||"};
String payload = "cmd /c whoami";
String cmdTemplate = "java -version %s " + payload;
String cmd;
Process p;
int returnCode;
for (String specialChar : specialChars) {
cmd = String.format(cmdTemplate, specialChar);
System.out.printf("#### TEST CMD: %s\n", cmd);
p = Runtime.getRuntime().exec(cmd);
returnCode = p.waitFor();
System.out.printf("RC : %s\n", returnCode);
System.out.printf("OUT :\n%s\n", IOUtils.toString(p.getInputStream(),
"utf-8"));
System.out.printf("ERROR :\n%s\n", IOUtils.toString(p.getErrorStream(),
"utf-8"));
}
System.out.printf("#### TEST PAYLOAD ONLY: %s\n", payload);
p = Runtime.getRuntime().exec(payload);
returnCode = p.waitFor();
System.out.printf("RC : %s\n", returnCode);
System.out.printf("OUT :\n%s\n", IOUtils.toString(p.getInputStream(),
"utf-8"));
System.out.printf("ERROR :\n%s\n", IOUtils.toString(p.getErrorStream(),
"utf-8"));
Result of the test:
##### TEST CMD: java -version & cmd /c whoami
RC : 0
OUT :
ERROR :
java version "1.8.0_31"
##### TEST CMD: java -version && cmd /c whoami
RC : 0
OUT :
ERROR :
java version "1.8.0_31"
##### TEST CMD: java -version | cmd /c whoami
RC : 0
OUT :
ERROR :
java version "1.8.0_31"
##### TEST CMD: java -version || cmd /c whoami
RC : 0
OUT :
ERROR :
java version "1.8.0_31"
##### TEST PAYLOAD ONLY: cmd /c whoami
RC : 0
OUT :
mydomain\simpleuser
ERROR :
Incorrect usage:
ProcessBuilder b = new ProcessBuilder("C:\DoStuff.exe -arg1 -arg2");
In this example, the command together with the arguments are passed as a one string, making it easy to manipulate that expression and inject malicious strings.
Correct Usage:
Here is an example that starts a process with a modified working directory. The command and each of the arguments are passed separately. This makes it easy to validate each term and reduces the risk of malicious strings being inserted.
ProcessBuilder pb = new ProcessBuilder("TrustedCmd", "TrustedArg1", "TrustedArg2");
Map<String, String> env = pb.environment();
pb.directory(new File("TrustedDir"));
Process p = pb.start();
In .Net use System.Diagnostics.Process.Start to call underlying OS functions.
var process = new System.Diagnostics.Process();
var startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.FileName = "validatedCommand";
startInfo.Arguments = "validatedArg1 validatedArg2 validatedArg3";
process.StartInfo = startInfo;
process.Start();
Where input validation cannot be relied upon, arguments can be made safe by escaping double quotes within the arguments and then surrounding each argument with double quotes.
// This list will contain the arguments which we want to escape before
// passing to the function
var arguments = new List<string>(){"Arg1", "Arg\"2&|", "\"Arg3\""};
// Escaping Stage 1: All " (double quote) characters within the arguments
// are doubled to escape them on the command line so they will only ever
// be interpreted as data and not as anargument separator
arguments = arguments.ConvertAll(s => s.Replace("\"", "\"\""));
// Escaping Stage 2: The arguments should be surrounded by " (double quote)
// characters to ensure that the data in the arguments cannot break out of
// the current argument
arguments = arguments.ConvertAll(s => $"\"{s}\"");
// Pass the escaped arguments to the "ProcessStartInfo" object
process.Arguments = string.Join(" ", arguments);
In PHP use escapeshellarg() or escapeshellcmd() rather than exec(), system(), passthru().
- OWASP Command Injection.
- C Coding: Do not call system().
- OWASP Testing Guide article on Testing for Command Injection.