Sometimes web applications need to communicate with the underlying operating system. This can either be to run system commands or to start applications written in another programming language such as shell, or execute a python script. In order to do this there are functions available that can execute a command that is passed to them as a shell command. While that is very useful functionality it is equally dangerous when not used correctly, and can lead to web application security problems, as explained in this article.
By exploiting a command injection vulnerability an attacker can abuse the function to inject his own operating system commands. This means he can easily take complete control over a web server, therefore developers should be very careful how to pass user input into one of those functions.
In this example of the command injection vulnerability we are using the ping functionality which is notoriously insecure on many routers. A common function exists that passes an IP address the user specifies to the system’s ping command. Therefore if the user specifies 127.0.0.1 as an IP address, the command will look like this:
ping -c 5 127.0.0.1
Since it is possible to break out of the ping command or provoke an error with useful information the attacker can use this functionality to execute his own commands. An example for adding a second system command could look like this:
ping -c 5 127.0.0.1; id
In the above example, first the ping command is executed and directly after that the id command is executed, therefore the response on the page will look like this:
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.023 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.074 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.074 ms
64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.072 ms
64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.037 ms
--- 127.0.0.1 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 3999ms
rtt min/avg/max/mdev = 0.023/0.056/0.074/0.021 ms
uid=0(root) gid=0(root) groups=0(root)
The attacker can also try an error based attack, that typically looks like the below:
ping -c 5 “$(id)”
The above returns a response like this:
ping: unknown host uid=0(root) gid=0(root) groups=0(root)
In order to prevent an attacker from being able to insert special characters into the command you should try to generally avoid system calls where possible. Under all circumstances avoid user input of any kind inside them unless it is absolutely necessary and deactivate that function in your language’s configuration file if you don’t need it. In some languages you can separate the execution of the process from the input parameters. You can also build a whitelist of possible inputs and check its format. For example integer for a numeric id.