Editor’s note: This is an updated version of Serge’s wildly popular article written originally in 2011, proving yet again that while the technology may change, security principles endure.
What are Command Injection vulnerabilities?
Command Injection is a type of vulnerability that allows attackers to inject arbitrary commands into a vulnerable software application or service and then have the malicious commands get executed with the vulnerable software’s privileges.
The higher the software’s privileges, the greater the access it has to resources in the environment. In some cases, there may be limits on the scope of available commands due to peculiarities of the application’s or the system’s inner workings. Still, most of the time an attacker gains command-line access to the system running the vulnerable application. This allows the attacker to take control of the software and all its data and also control of other resources on the system that the software has access to.
Exploiting Command Injection also creates the possibility of leveraging additional Privilege Escalation exploits to take full control of the target system. If the vulnerable software is running with elevated privileges, then exploiting Command Injection by itself is typically sufficient to take full control of the target system.
Command Injection is one of the most dangerous and common vulnerability types. It is easy to find and exploit; its severity cannot be overstated. Command Injection is assigned CWE-78 in the Common Weakness Enumeration and is included in the A1: Injection category as the top threat in every version of the Open Web Application Security Project (OWASP) Top 10 project.
Where are Command Injection vulnerabilities found?
Command Injection vulnerabilities may be present in any software that invokes external commands. The term “external command” is used in this article to describe any command that is not a part of the application being tested and is therefore external to the application.
Any of the following scenarios can cause command Injection vulnerabilities:
- The software invokes a system shell to execute an external command and passes untrusted data as an argument to the external command.
- The software uses untrusted data to determine which external command to execute.
- The software passes untrusted data to an external command, and the external command will invoke additional commands based on untrusted data.
Strong data validation might cause a Command Injection vulnerability to be unexploitable in practice, even if any of these scenarios are present. However, to fully mitigate the threat, code that contains any of the scenarios that cause Command Injection must be (re)written using safe programming patterns.
How to test for Command Injection vulnerabilities
Prepping your test environment
Before you begin testing for Command Injection vulnerabilities, you should know that while the general testing process is always the same, the actual testing process differs slightly depending on how much access you have to the software, its source code, and the environment in which it operates.
With more access, it is easier to identify potentially vulnerable sources of input, craft attack strings, and observe changes to system behavior in response to your attack strings. For example, suppose you have access to the environment in which the application being tested is running. In that case, you can observe whether your attack strings force the application to create a file in a temporary directory or not. That might allow you to determine that Command Injection is present even if the application does not otherwise produce any indication that an error condition has taken place.
Access to the source code might help identify code that passes input to external commands, making it easier to identify which inputs to focus testing on and which test strings to use.
So, when testing in real-world scenarios, try to get as much access and as much information before you begin testing.
The Testing Process
The following diagram illustrates the general testing process. We will use the same process for uncovering Command Injection vulnerabilities.
General Testing Process
Select Entry Point
An entry point into an application is simply a channel via which a software accepts external data. Each entry point will usually have multiple sources of input that supply data to that entry point. In this step, it is important to pick an entry point into the application and then iterate through input sources.
Identify suitable sources of input
Our focus needs to be on sources of input that supply text-based data to the software. Input data that cannot be interpreted as plain text, such as clicking on UI elements, are not relevant to our testing.
For example, for a web application, the input sources will typically be the various HTTP request parameters sent from the client to your web application. When identifying HTTP request parameters, don’t overlook the requests that are sent by JavaScript.
Prioritize the sources of input
Priority should be given to inputs that are known to be processed in contexts that are susceptible to command injection, such as being used as arguments when executing external applications. When attempting to determine the context in which the input is going to be processed, it helps examine the output that results from processing the input data.
In some cases, there might be obvious indications of system commands being invoked by the software. For example, suppose an application takes the name of a directory as input and then produces a list of filesystem objects inside that directory that looks identical to the output produced by executing the ls -l
command. In that case, there is a strong possibility the application is passing the name of the directory to the ls -l
command.
Some common functionality implemented by invoking external commands includes converting media formats, file archiving, making backups, especially database backups, sending email, and using other non-web network protocols.
While you should prioritize input sources that are likely to be used for passing arguments to system commands, you should really test all sources of input to the application that you can control, unless it is known for a fact that some specific inputs will not be used when invoking external commands or it is not necessary to test the application thoroughly.
Craft Payload
To begin testing, you will craft payloads that will be sent to the selected entry point. For uncovering Command Injection weaknesses, our payloads will be in the form of attack pattern strings. Attack pattern strings are nothing but specially crafted text data that contain special characters or statements that are likely to cause a deviation from the code author’s intended action. This deviation could either cause the targeted code to fail during execution or cause the targeted code to invoke arbitrary external commands. Most attack pattern strings used to test for Command Injection vulnerabilities contain shell metacharacters (such as semicolons) and names of commands.
There are many collections of test strings that can be used to detect Code Injection, available on the Internet. One popular public collection of test strings is located at https://github.com/fuzzdb-project/fuzzdb/tree/master/attack/os-cmd-execution. There are multiple files in that repository, but you are mainly supposed to use command-injection-template.txt
and substitute your own commands in place of {cmd}
. Some of the other files are suggestions of commands that can be used in place of {cmd}
, but basically, any command that you can invoke in a command line terminal that you wish to execute with the privileges of the potentially vulnerable application can be used.
Depending on the application and platform, the test strings might have to account for some limitations like being unable to include white-spaces or some other characters inside test strings. Overall, some tailoring is required, but simply inserting valid commands that are likely to produce observable changes in a vulnerable application’s behavior in place of {cmd}
in command-injection-template.txt
will give you a good starter set.
Execute Test
In addition to collections of payloads, you will also need tools for sending the test strings to the application. The tools depend on the application being tested; for example, to test a web application you can use a web proxy, a web browser, or write your own scripts. Each type of tool has its own advantages and disadvantages, and you are not limited to only one type of tool. Whatever tools you use, the goal is to send payload as input to the application.
Observe System
Most likely at first your attack pattern strings won’t actually produce valid commands when they trigger vulnerable code and will result in application errors when processed. Depending on how the application handles errors, you may or may not receive useful error messages or other useful feedback. If the application displays an error message other than input validation error when processing any of your test strings, then most likely Command Injection is present. You should continue testing by modifying the attack pattern string that triggered the error message.
Similarly, suppose there are other obvious changes in application behavior that indicate that the application is not functioning correctly in response to any of your test strings. In that case, Command Injection is most likely present, and you should keep testing by modifying the test string that triggered the change in behavior.
In some cases, the application will appear to function correctly, even though a vulnerability is present. You can still discover vulnerabilities that do not cause obvious changes in application behavior by passing test strings that perform actions to detect if a vulnerability is present. For example, you might try to pass a test string that creates a new file on the filesystem used by the application, and if the file appears after you send your test string to the application, you know that a vulnerability is present.
If you cannot detect any deviation in system behavior, you should repeat the test with the rest of your collection’s attack pattern strings. If no attack strings produce any indication of Command Injection, you should select an alternate entry point for testing and repeat the test. As illustrated in the diagram, this is part of the general testing process. After you have exhausted all possible combinations of payloads and input entry points, the test can be considered complete. Note that even if you have not found any Command Injection, that does not mean that Command Injection is not present in absolute terms, but simply that none has been found. The methodology described here is employed both by human testers and by automated scanning tools when testing for Command Injection. Still, the actual assurance provided by a real-world test depends on factors such as the tester’s skill and the quality of the preparation work, such as developing the payloads, the ability to monitor the behavior of the system being tested, and accurately identifying all the sources of input to the software being tested.
Create PoC
If you discover sources of input to the application that result in error conditions or incorrect behavior when attack pattern strings are passed, then you should tailor such test strings into Proof-of-Concept (PoC) exploits to verify that a vulnerability is present. Causing error conditions or incorrect behavior in vulnerable code is known as “triggering the vulnerability”.
If you are able to write a functioning exploit, then the exploit is proof-positive that you have discovered Command Injection. Even if you cannot write a functional exploit, you should consider your finding to be a potential, though unverified, Command Injection vulnerability.
Let’s look at an example. We are going to use PHP code that actually contains a Command Injection vulnerability. The vulnerable PHP script is a part of a hypothetical web application, and all this script is supposed to do is use the system ping utility to determine whether the host on which the web application is running has network access to a user-specified network address.
The filename of the script is ping.php
, and it is located at servername/ping.php
. The contents of the script are as follows:
<html><body><pre>
<?php
$command = "ping -c 1 " . $_REQUEST["address"];
system($command);
?>
</pre></body></html>
Step 1.
Either by inspecting the running web application or by analyzing the source code, we have determined that one of the inputs to the application is the HTTP request parameter called address
. This will be our entry point.
By observation or viewing the source code, we can also confirm that the script ping.php
invokes a system command. Because of this there is a very strong possibility that a Command Injection vulnerability is present in this script, and we will prioritize testing it and test it immediately.
Step 2.
Let’s send some test strings to the script. Since we can manipulate the request parameter address
using a regular web browser, we don’t need to use any special tools. We are going to use a test string in the form of:
;echo COMMAND INJECTION IS PRESENT;
To send this test string to the application, we access the following URL in our web browser:
http://servername/ping.php?address=;echo COMMAND INJECTION IS PRESENT;
The reason for that first semicolon is to cause whatever follows the semicolon to be interpreted as a command if Command Injection is present. The echo command is the command that we are attempting to execute, and it is useful because it might return our message to us, indicating that we executed our command successfully.
The reason for that last semicolon in the test string is that it helps make sure that your chosen command will execute in the event that the vulnerable script appends something to the string you’re injecting before the data is used to execute system commands. The web browser and the application server will conveniently take care of handling the white spaces in the HTTP request parameter for us.
Step 3.
After accessing the crafted URL that issues a request containing our test string to the target application, we observe the application behavior in the form of the output produced by the application and displayed in our web browser.
The output when a valid address, such as localhost, is passed to the script looks like this:
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.031 ms
--- localhost ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.031/0.031/0.031/0.000 ms
The output when the test string ;echo COMMAND INJECTION IS PRESENT;
is passed to the script looks like this:
COMMAND INJECTION IS PRESENT
We have clearly manipulated the application behavior in a way that indicates that Command Injection is present.
Step 4.
To confirm that we have discovered Command Injection, let’s attempt to execute arbitrary commands similar, but not identical, to the ones issued by real-world attackers since we don’t want to cause any actual harm. Instead, we simply want to prove that we can manipulate the application in a potentially dangerous way. We also don’t really need that last semicolon that is present in our test string since nothing is being added to the end of our exploit by the application, so let’s get rid of it.
Let’s try reading the contents of a system file using the cat /etc/passwd
command.
Our exploit will then look like this:
http://servername/ping.php?address=;cat /etc/passwd
The application responds:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
...
Let’s execute the whoami
command to determine our system privileges. The exploit will look like this:
http://servername/ping.php?address=;whoami
The application dutifully responds as follows, showing the user as whom the command injection is being executed:
www-data
This shows that we can manipulate the vulnerable application confirming that a Command Injection vulnerability is present. If our test’s purpose was penetration testing, we can report our discovery and possibly attempt to gain additional leverage. If we are participating in a competition, we can attempt to craft an exploit that will capture the flag. If we are responsible for the security of the application, we should fix the vulnerable code. Whatever the intent of the test, the next step is to fulfill it.