Me

Stefan

Cybersecurity Specialist

Magpie CTF 2022

A jeopardy-style CTF hosted by the University of Calgary.

Stefan

12-Minute Read

Magpie CTF

About

Magpie CTF was a jeopardy-style CTF held virtually on February 25th to the 27th. It featured challenges from various different categories such as web application, reverse engineering, OSINT, etc. It was a team based competition primarily made for students, with no entry fee.

The competition told a heist themed story where a fictional company (Mom & Pops Flag Shop), acquired many other flag companies across the nation and achieved a monopoly in the flag market. Our mission as the competitors was to:

  • reclaim the flags that Mom & Pops Flag Shop acquired
  • find out more who exactly “Mom” and “Pop” were
  • and figure out how they managed to corner the market

This story would unravel with each challenge, giving more and more hints as to what the company has done; Elements like these are what made Magpie CTF stand out in my eyes.

As more and more competitors solved challenges, the point value for those challenges would start depreciating. This would also lower the value they held for the competitors that already solved the challenge, ultimately lowering the value of the challenge as more and more people solved it.

During this CTF, the organizers would stream a variety of heist movies such as: Ocean’s 11, Mission Impossible, Inside Man, Inception, etc; as well as status updates and SkipTheDishes gift card giveaways to lucky competitors.

Prizes

The prizes for this CTF were as follows:

Placement Prize
1st Place $1024
2nd Place $512
3rd Place $256

There were also an additional 3 prizes of $128 being handed out for the top 3 challenge write-ups.

Results

My team and I placed 16th by the end of the competition.

error loading image

error loading image

error loading image

Even though our placement wasn’t anyting remarkable, we still walked away with a unique experience very few other CTFs had.

error loading image

Challenges

Here were some of the challenges we solved that I thought were interesting.

Blame The Intern - Web

This challenge tasked us with escaping a Python application written with Flask. This application simply took a query we gave it and generated a fake flag.

error loading image

error loading image

We were also given the source code of the application, with the following snippets of interest:

# Handle app state
STATE = {
    "success": False,
    "id": 56645,
    "username": "timtheintern",
    "secret": "THISISASUPERSECRETKEY",
    "flag": os.environ['FLAG']
}

...

# Handle POST requests to /submit
@app.route("/submit", methods=["POST"])
def submit():
    result=request.form.to_dict(flat=False)
    STATE['result'] = result
    input_string = f"{result['try'][0]}"
    count_string = f"{gen_useless_flag()}"
    return render_template_string("<!DOCTYPE html>" \
                                  "<html>" \
                                  "<head>" \
                                  "<title>Flag Licenser</title>" \
                                  '<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">' \
                                  "<head>" \
                                  "<body>" \
                                  "<!-- This is our default flask template -->" \
                                  "<!-- Remove template string rendering or sanitize input before pushing to production. -->" \
                                  "<div class='w-full lg:w-1/3 mx-auto my-8 bg-gray-300 lg:border-0 lg:rounded-xl p-4'>" \
                                  "<div class='text-center'>" \
                                  "<span class='text-4xl'>Results</span><br>" \
                                  f"<span class='text-md'>Your key is: </span><br>" \
                                  f"<span class='text-xl font-bold'>{input_string}</span><br>" \
                                  "<span class='text-md'>The flag is: </span><br>" \
                                  f"<span class='text-xl font-bold'>{count_string}</span><br>" \
                                  "</div>" \
                                  "<a class='button' href='/'>Try Again</a>" \
                                  "</div>" \
                                  "</body>" \
                                  "</html>", state=STATE)# Handle POST requests to /submit

The parts of focus in this case are the STATE dictionary defined at the top of the file, the line in the reponse containing our query, and the state variable defined at the end of the response.

Looking at the variable defined at the end of the response, it simply assigned the dictionary defined at the start of the script. This dictionary held a key value pair containing the flag stored in an environment variable. To call this key, we used the following query:

{{state['flag']}}

This called the flag key in the state dictionary and returned us the flag.

error loading image

Tracking the CEO - OSINT

This challenge was broken down into a few parts, with the ulimate goal being to track down the CEO and find their real identity.

error loading image

The first thing we did when tackling this challenge was look for any social media accounts that may be related to the target. We came up with a bunch of username combinations, one of which yielded many results.

error loading image

As we went down the list and started to check the Instagram result, we noticed that the account was both validm, and had a bunch of interesting posts.

error loading image

The first of which was an image of a building. We started to investigate this image since it might clue us in as to where the home city of Mae is.

error loading image

Looking at this image, we can tell that the CEOs home city was somewhere in Ukraine after translating the text written on the building. At this point, we started taking guesses by picking and choosing the most populated cities.

Reading this challenge’s hint, the flag would be somewhere on the Wikipedia page of Mae’s home city.

After about half an hour of looking through Wikipedia edit history, we finally found the flag within the Lviv page edits.

error loading image

Along with the flag, a Discord server link was also included in the edit.

error loading image

Entering this server, the only access we had was the ability to send messages in a single channel that would get checked against a password. If the message was not the password, it would simply get deleted.

We now started looking back to our previous social media results to see what we can go off of, and we noticed a video that Mae had on their Instagram. Playing this video, it was a bunch of different beeps with a random order.

error loading image

Looking at the description of the post, we found that the beeps were coming from an old flip phone, emulating a message being sent.

After doing some research, we found that these “beeps” were DTMF codes, which meant that we were able to decode each beep into the number that was pressed on the flip phone. Then, it was just a matter of counting the amount of times a number was pressed to derive a letter.

To first decode each beep, we downloaded the video and converted it into a sound file.

Then, we downloaded dtmf-decoder off of Github, and fed the sound file to it.

error loading image

Finally, we derived the letters corresponding with each number and the amount of times it was pressed.

error loading image

This means that:

  • 99 -> x
  • 5 -> j
  • 8 -> t
  • 66 -> n
  • 4 -> g
  • 7 -> p

Entering xjtngp into the Discord server gave us a role that allowed us to see other channels in the server, and in turn revealed the next flag.

Scattered Letters - Networks

This challenge involved a custom made mail server and web app. Our goal was to retrieve the flag hidden in another users email.

error loading image

To complete this challenge, we simply explored the web app by first registering an account and then checking our emails.

error loading image

error loading image

Being able to check our emails, we now have the email address of the IT department as a target for privilege escalation.

To perform privilege escalation, we analysed the login flow of the email service and began tampering with the headers. One of the requests that was sent during the login flow was a POST request for an endpoint that lists emails containing the user ID of the end user. We simply intercepted this request during the login and changed it to the email address of the IT department we found earlier.

As a result, we were able to see the emails belonging to the IT user.

error loading image

The request that would get sent whenever a user would try to view an email had a similar flaw, and so we exploited that flaw to check the IT user’s emails.

error loading image

error loading image

While there was nothing of interest in the emails (yes, we tried those passwords), we were given a new email address to escalate to, the administrator.

Using the same method, we were able to find the flag in the administrator’s emails.

error loading image

error loading image

Mom & Pops HQ - Reverse Engineering

This challenge would give us an ELF binary that we would need to reverse engineer to figure out what the flag is.

error loading image

Running the binary, it would prompt us asking for a key code. We assumed that entering the correct key code would return the flag.

error loading image

After trying a bunch of simple commands to try and get the flag (strings), we decided to import the file into Ghidra and tried to see where the key code comparison was made.

The farthest we got before the next step was a few functions that checked the input for special conditions.

One of the functions would check if the input was 8 characters long (line 16):

error loading image

Then it would call a function with our input passed into it to check if each character matches the keycode.

error loading image

Each function would just return a single character.

error loading image

At this point, my teammate relayed to me all the possible characters and the length of keycode, so I decided to make a Python script that would try all the possible combinations of the given letters to save time. The following script took a few minutes to fully complete:

#!/usr/bin/python3
import subprocess
from itertools import permutations

values = permutations(['O','M','I','C','R','O','N','P'])

for i in list(values):
        print()
        value = "".join(i)
        subprocess.run("echo 'Now trying: {value}'".format(value=value), shell=True)
        subprocess.run("echo '{value}' | ./CONFIDENTIAL_BLUEPRINTS".format(value=value), shell=True)

error loading image

The keycode ended up being OMNICORP (go figure…), and the flag ended up being encoded as ASCII art.

Alternative Solution

Alternatively, the solution could have been discovered by manually sorting each character position with the characters that each function returned.

error loading image

Mission Impossible - Miscellaneous

This challenge gave us a Python script that was running a web app. Our goal was to “deactivate the lasers”, which referred to a YouTube livestream that would play a bunch of lasers guarding a flag.

error loading image

Presumably, we needed to somehow break the web app in order to deactivate the lasers. One of the functions defined in the source code was named shutdown, which we assumed to be the function we needed to successfully call in order to shutdown the lasers.

@app.route('/api/v1/security-controls/shutdown', methods=['POST'])
def shutdown():
    if 'X-API-Key' in request.headers and request.headers['X-API-Key'] == CONFIG['API_KEY']:

        request_data = request.get_json()

        if request_data and 'lasers' in request_data:
            lasers = request_data['lasers']
            return laser_control.shutdown_lasers(lasers, request)
        else:
            return "Invalid JSON body data!\n"
    return "You are not authorized to turn off the lasers!\n"

We were not given any API keys that we could have used to call this request and pass the authorization control. However, we noticed that at the top of the script, an API key variable was declared.

CONFIG = {
    'API_KEY' : ENV_VALUES['API_KEY']
}

Our goal now became to somehow call this variable; It was declared globally, so it was possible to call it within a function.

We then scrolled further down the code and read a function named format_employee

# !!! EXPERIMENTAL !!!
#
# This API endpoint is functional but it has not been audited by our security team.
# While it is functional, we can not guarantee that there are no vulnerabilities.
@app.route('/api/v1/employees/format', methods=['GET'])
def format_employee():
    if 'template' in request.args:
        template = request.args['template']
        return template.format(person=employees[0])
    else:
        return "Error: No template parameter provided. Please specify an template.\n"

Apart from the obvious comment, we knew that this was the function we needed to attack due to the use of .format on the template variable. This variable would contain our user input when calling the endpoint.

After providing the parameter template, this script would grab the first item from the list of employees. This list of employees would simply contain objects with attributes containing the employee name, ID, and position.

class Employees:
    def __init__(self, name, id, position):
        self.name = name
        self.id = id
        self.position = position

    def serialize(self):
        return {
            'name' : self.name,
            'id' : self.id,
            'position' : self.position
        }

    def get_id(self):
        return self.id

employees = [
    Employees('Ma', 0, 'CEO'), 
    Employees('Pa', 1, 'CEO'),
    Employees('Jim', 2, 'Marketing'),
    Employees('Diane', 3, 'Clerk')
]

This would only work as expected if we passed {person} as the parameter value, since it formats that part of the string to be the list item.

error loading image

error loading image

Otherwise, it would simply reflect the given value.

To break this, we simply called the object with the __init__.__globals__ attribute.

error loading image

error loading image

Now that we have the API key, we proceeded to call the shutdown function we saw earlier.

We ran into a few hiccups along the way, such as not being sure what to enter into the json body.

error loading image

But eventually, we found a function api_docs, which revealed the laser naming schema.

@app.route('/api/docs', methods=['GET'])
def api_docs():
    return render_template('api-docs.html')

error loading image

error loading image

The last control we had to bypass was a rate limiter that would only allow us to shut off two lasers every hour.

To bypass this, we simply passed a list of lasers similar to the docs, but with all 4 lasers instead of only 2.

error loading image

Crack the Safe - Miscellaneous

This challenge had us navigating to an oddly configured web server that would return a response every time we would enter a single character into the prompt.

error loading image

error loading image

Looking at the HTML source code of the webpage, we spotted something immediately out of the ordinary.

error loading image

Line 35 contained a “hidden” image element. We ended up downloading and analyzing it.

error loading image

Using binwalk, we were able to find and extract some hidden files within the image.

error loading image

One of the hidden files we found was some PHP source code.

<?php
$fp = fopen('./flag.zip', "rb");
$binary = fread($fp, filesize('flag.zip'));
$target = base64_encode($binary);
if (isset($_GET['userinput']) && substr($target, 0, strlen($_GET['userinput'])) == $_GET['userinput']) {
        echo "<input type='text' id='userinput' name='userinput' oninput='doSubmit()' value='".$_GET['userinput']."' autofocus /><br>";
        if ($target == $_GET['userinput']) {
                echo "<b id='useroutput' class='someClass' style='text-align: center; color: #00FF00;'>" . $_GET['userinput'] . "</b>";
        } else {
                echo "<b id='useroutput' class='someClass' style='text-align: center; color: #FF0000;'>" . $_GET['userinput'] . "</b>";
        }
} elseif (strlen($target) < strlen($_GET['userinput'])) { 
        echo "<input type='text' id='userinput' name='userinput' oninput='doSubmit()' value='".$_GET['userinput']."' autofocus /><br>";
        echo "<b id='useroutput' class='someClass' style='text-align: center; color: #00FF00;'>" . $target . "</b>";
} else {
        echo "<input type='text' id='userinput' name='userinput' oninput='doSubmit()' value='' autofocus />";
}

After realizing that the web app doesn’t actually do anything (entering the correct input just makes it turn green), we explored the flag.zip file referenced at the top of the file. We did this by simply adding flag.zip to the URI in order to download the file. Once downloaded, we tried to unzip it, but it was encrypted.

To crack it, we first extracted the hash from the file, and then ran john against it with the rockyou wordlist.

error loading image

Conclusion

Even though we did not have as much time as we wanted to participate in this CTF, it provided an interesting story, as well as some awesome challenges to keep us on our toes. Definitely one of the better CTFs we’ve come across.

Recent Posts

Categories

About

A blog made for posting tutorials about various topics.