HackTheBox: Emdee Five For Life Challenge - Writeup

After my little excursion into Reversing, I was up for some easy Web challenge. Emdee Five For Life is just that easy web challenge I was looking for.

Initial overview

Starting the instance and opening up the webpage reveals the following:

Main Page

Our goal is to MD5 encrypt the presented string (which changes every time we refresh the page), input it into the given text field and submit. In this case, the string ‘KPjxIGpznIR0u8fRox5G’ gives us an MD5 hash of ‘1b5a3bb73b72c6d2acd7f6dbbee5b81c’. However, it tells us we’re ‘Too slow!’ when inputting the final hash. Notice that it also tells us we’re too slow if we input any random characters, suggesting that there’s no actual error message if the hash is wrong.

Solving the challenge

It’s obvious that we are always going to be too slow to encode and paste the final MD5, so we’ll have to resort to scripting to solve this challenge. My first choice for this is Python 3, but any language where you can quickly send requests (GET/POST) and encode strings to MD5 will work.

Taking a step back: Recon

Before we dive in and cobble something together, we’ll have to see what is actually being sent when we submit a hash:

Submit Request

As we can see, it’s a POST request with a PHPSESSID cookie; the name of the input field is hash, so 'hash': 'A' is what’s being sent in the request body.

Armed with this info, we can hop into Python and start scripting.

The Basics: A Session

This section is going to be a bit forward-looking, since I didn’t figure this one out until quite a bit of trial and error. For this to work, we’ll need to make use of the requests library, and more specifically the Session. This allows us to carry over cookies from one request to the other, without having to manually read and save them each time. The PHPSESSID cookie needs to carry over to the final POST request.

Getting the document and parsing the string

Let’s start by simply GETting the website. Note the IP and Port will be different for you depending on your instance, so just insert the correct one as you go.

In Python 3, it is as simply as this:

import requests

session = requests.Session()
r = session.get('http://<IP>:<PORT>/')

We’ve now created a Session object and performed a GET Request on the website. Using r.text, we can output the content of the website in full:

<html>
    <head>
    <title>emdee five for life</title>
    </head>
    <body style="background-color:powderblue;">
        <h1 align='center'>MD5 encrypt this string</h1><h3 align='center'>SrT6SvJGrkUg6uINMnUA</h3><center><form action="" method="post">
        <input type="text" name="hash" placeholder="MD5" align='center'></input>
        </br>
        <input type="submit" value="Submit"></input>
        </form></center>
    </body>
</html>

Our job now is to parse the string found within the h3 tags, encode it as MD5 and send it off. You can definitely use BeautifulSoup4 for this, making the call as simple as soup.h3.string after you’ve parsed the HTML into the soup variable using BS4. However, as I initially had issues with the script being slow, I resorted to manually parsing out the string using regex.

Let’s create a new function called parse_and_encode(text) for this purpose, with the HTML document as argument. To find and extract the string in between the <h3> tags, we can compile the following regex, then simply use findall() and pick out the first (and only) value out of the resulting array:

def parse_and_encode(text):
    reg = re.compile(r"<h3 align='center'>(.*)</h3>", re.MULTILINE)
    text = reg.findall(text)[0]

So far so good! If we print out the variable text, we should, in this case, see the string SrT6SvJGrkUg6uINMnUA pop up on our console. For the next step, we’ll make use of the hashlib library. With this library, encoding to MD5 is as simple as one function call, with an additional one to return the hex digest. It looks like this:

import hashlib

def parse_and_encode(text):
    reg = re.compile(r"<h3 align='center'>(.*)</h3>", re.MULTILINE)
    text = reg.findall(text)[0]
   +h = hashlib.md5(text.encode('utf-8'))
   +dig = h.hexdigest()
   +return { 'hash': dig }

Note that all text in Python 3 is Unicode, necessitating an encoding to UTF-8 before finally encoding the string as MD5. This is probably not necessary in Python 2. Hashlib returns an object, so we’ll have to call hexdigest() to return the actual hash. As you can see, we’ll also return the hash in a format that can immediately be used in a POST Request later on.

Sending the final request and extracting the flag

Now that we have our hash ready, we just need to send a POST Request using our existing session and extract the flag. Adding it below our existing GET Request, we get this:

import requests

--snip--

session = requests.Session()
r = session.get('http://<IP>:<PORT>/')

+data = parse_and_encode(r.text)

+r2 = session.post('http://<IP>:<PORT>/', data=data)

At this point, you can simply print out r2.text and should be able to see the flag, if everything worked out fine. As an additional fun step, you can compile another regex and extract the flag out that way, printing only the flag to console.

The final script to solve the challenge looks like this:

import requests
import hashlib
import re

def parse_and_encode(text):
    reg = re.compile(r"<h3 align='center'>(.*)</h3>", re.MULTILINE)
    text = reg.findall(text)[0]
    h = hashlib.md5(text.encode('utf-8'))
    dig = h.hexdigest()
    return { 'hash': dig }

session = requests.Session()
r = session.get('http://<IP>:<PORT>/')

data = parse_and_encode(r.text)

r2 = session.post('http://<IP>:<PORT>/', data=data)
reg = re.compile(r"<p align='center'>(.*)</p>", re.MULTILINE)
flag = reg.findall(r2.text)[0]
print(flag)

Conclusion

Easy, fun and quick to solve. Remembering to keep the PHPSESSID cookie across requests took a bit of time, and quite often I’d still hit ‘Too slow!’ even though I’d encoded everything properly and it was reasonably fast.