Metasploit Community CTF December 2021

Time for another CTF! This year Alex and I joined forces as SKUA, our little two person team. This was our third Metasploit CTF, you can see the writeup for last year here: Metasploit CTF December 2020.

This year we found 12 of the 18 flags and came in 26th place out of the 265 teams that got at least one flag.


Getting Started

Disclaimer: I am not a programmer! I have done my best to understand and explain these challenges. However, my explanations might be a bit basic and/or inaccurate.

This year I had a better understanding of ssh tunnelling and some of the command line tools. They are explained in more detail in last year’s writeup. We connected to the kali jump box with this command:

ssh -i Downloads/metasploit_ctf_kali_ssh_key_2021.pem kali@

Then to load up a website on our local browser we used ssh tunnelling with this command and went to localhost:8080 on our browser.

ssh -L 8080: -i Downloads/metasploit_ctf_kali_ssh_key_2021.pem kali@ 

This got us our first flag.

Port 80 – Simple Website: 4 of hearts

A simple website with the flag.

Port 443 – Git repository: 2 of spades

This was not a website, but the detailed nmap we ran gave us some useful information.

443/tcp   open  ssl/https  Apache/2.4.51 (Debian)
| http-git:
| 	Git repository found!
| 	Repository description: Unnamed repository; edit this file 'description' to name the...
|_	Last commit message: More enthusiasm
| http-methods:
|_  Supported Methods: GET POST OPTIONS HEAD
|_http-server-header: Apache/2.4.51 (Debian)
|_http-title: Site doesn't have a title (text/html).

Okay, a git repository. Alex tried to clone it so he could easily browse through everything, but that command didn’t seem to work. Here, I will let him explain.

Alex’s Notes

I tried to use git clone a few different ways but it didn’t seem to work. Not sure if I was doing something wrong or if it was a weird configuration. But I could see the .git/ directory and all of the files in it, like HEAD, so I figured there was a way to dump the repo.

I found this article which led me to this tool which walked through the repo and essentially cloned it to the local machine. From there, I took a look at the history.

There was a .env file that was included in the initial commit and deleted in the “Cleanup” commit:


Put that filename in the URL, and there was the flag! 💥

Port 8080 – Cookies Galore: 9 of diamonds

What is with these websites that make me hungry? This is obviously a new take on the ‘salt free hashes’ from last year. I wanted cookies for the rest of the afternoon.

This page had a sign-up page, sign-in and admin. Trying to navigate to the admin page resulted in “Unauthenticated users cannot access this page.”

I signed up using ‘’ and ‘password’. Then I signed in using my credentials. Nothing happened, still couldn’t access the admin page. Alex showed me how to use the inspector to look at the cookies and there was one for ‘admin’ that was set to false. We double clicked, changed it to true and reloaded the page. Then visiting the admin page revealed the flag.

Port 10010 – Login 1: 4 of Diamonds

A simple website with login and registration page.

Registration and logging in brought us to another simple page.

I figured out that we could register multiple times with the same user and it just changed the number in the url. This didn’t seem to get us anywhere though. However, looking at the source gave Alex an idea.

The form was sending information up to account[]. Could there be other fields that we could fill in? Adding another line to the html in the inspector allowed him to add another field to the registration page. Alex sent ‘admin’ up as the account[role] for the new user.

Sure enough, when logged in with the new user an ‘Admin’ button was visible. Clicking on it revealed the flag.

Alex’s Notes

This was something I recognized from my Ruby on Rails days. I noticed that the site was running on Rails, and seeing the form with the account[...] format reminded me that this used to be a big problem in Rails. Basically, the code to handle the POST from the form might look like this:

def create
  @account =[:account])

By default, all fields submitted in params[:account] (which come from the HTML fields with names of account[...]) will be set on the new Account record in the database. Meaning that I could just include account[role] in the form and set it to whatever I want.

These days, Rails requires you to whitelist the parameters to use for mass assignment, which mitigates this issue.

Port 11111 – Login 2: 5 of Diamonds

This simple website looked a lot like the last one.

Registering gave some pretty standard news.

Not much to see. Trying to register with ‘admin’ results in ‘cannot register’! Now that ‘sarah’ is also a user, I can’t register again with that name. That means admin must be another user. There are limits on passwords and usernames, usernames are limited from 5 to 20 characters.

Alex tried to brute force the login with a password list and ended up breaking the site. We worked on some other challenges for a while and eventually had to restart our target machine to get this port to work again.

Alex also messed with the cookie some. It included the username in the cookie, which seemed odd. Changing that username to admin and resubmitting didn’t work. The username must be bundled in somewhere else so the check on the server doesn’t succeed.

Tried sqlmap on both login and registration page with no success. Could try higher levels of it, but didn’t think that would help. Once the site was working again I tried nikto. Nothing interesting from that.

Then I had a breakthrough: “Hmmm, this site will let us put in funny characters in the username and password. The previous one wouldn’t let us, so I hadn’t tried fuzzing this one properly. Must have been getting them confused.

Now I just have to find a character that is… null or something. So that we can trick the cookie into putting admin in as the username even though admin is already taken. A blank space after the username doesn’t do it, before doesn’t work either.”

Got an internal server error with this username and password: `;:'”~[]{}\|/?,.<>=

After a bit of trial and error I narrowed it down to the single quote ‘ so that must be sql injection? Maybe. Yes, Alex eventually got it to log in without registration. The cookie was still using the username we put in with the injection code in it, so didn’t pass the ‘admin’ test. Luckily the password field had the same issues, so we just used ‘admin’ as the username and put the nasty code in the password field which allowed us to log in with username admin. Sure enough the admin panel showed up and the flag was there.

password’ OR 1=1 ;--

Alex’s Notes

This site was written in Ruby, and the SQL query was probably set up something like this:

sql = "SELECT * FROM users WHERE username = '#{username}' AND password = '#{password}'"

Look innocent enough, but anyone familiar with SQL injection can see pretty immediately that this is insecure. Adding the password above turns the query into the following:

SELECT * FROM users WHERE username = 'admin' AND password = 'password' OR 1=1 ;--'

The above query will return successfully, which is all the server needed in order to log us in as the admin user!

Port 12380 – Terrible Webpage: 10 of Clubs

A terrible webpage indeed. The title even had a <blink> tag that would make it flash if the browser knew how to do that (mine didn’t).

This website was just static, it didn’t seem to be doing anything interesting. Looking at the source code didn’t reveal anything except a small comment <!– I commend the research, but the answer is not here. –>

The nmap result showed this website was running on Apache/2.4.49 (Debian). I did a quick search and found it was out of date. A little more digging revealed a critical path traversal and file disclosure vulnerability (CVE-2021-41773). There was even a metasploit module to exploit it!

Unfortunately I ran into some roadblocks. Metasploit on the kali machine doesn’t seem to have that module. Trying to install it gave me some trouble too. I wasn’t sure what I was doing wrong.

Then Alex had a crack at it. He got the metasploit module working, but it said the site wasn’t vulnerable. It seemed like a good track since it was on an outdated Apache with a recently exposed vulnerability. Alex kept trying some things manually, try harder!

Sure enough, it was the right track, eventually Alex noticed that files that should be there were returning a 500 response rather than 404 or something. He did some googling and found a curl command that worked. He got remote access and dug through the file system, eventually finding a secret folder with the flag!

Alex’s Notes

I found this article which helped with the RCE vulnerability. Found the flag at /secret/safe/flag.png using the following curl command:

curl -X POST -v --path-as-is "" -d "echo;cp /secret/safe/flag.png /var/www/html/"

Port 15000 – Student Database (Unsolved)

So much for the challenges being in order of difficulty. These three 15000 ports gave us a lot of trouble. They might not have been so hard for other people, but were definitely a challenge for us. This one required a lot of probing, and just finding that trick to get in.

This one was not a website. We used netcat and found it was a text interaction port.

Selecting different options allowed us to perform different tasks. Showing student records revealed there were no records (until we created one). Option 3 was not implemented.

input: 1

Creating new student record (alpha-numeric only):
Student name: sarah
Student surname: sarah 
Student degree: engineering
Student grade: 8

Creating new student record completed!

Input: 2

1. Single record retrieval
2. Show all records

Input: 1

Insert the student's details below.
Student name: sarah
Student surname: sarah

Found student in file: sarah_sarah.txt

Input: 3

Not yet implemented.

Input: 4

Deleting a student with the following details:
Student name: sarah
Student surname: sarah

Found student file: sarah_sarah.txt

Eventually we found the student name and surname creation could be overflowed if we entered enough characters. It resulted in the program hanging and not creating the record. None of the fields would accept odd characters and inputs 6-9 did nothing.

We poked and prodded at this for a long time, but it seemed like a black box. We just weren’t getting anywhere. We only found the one way to break it and it didn’t do us any good.

Port 15010 – Super-Secure File Storage (Unsolved)

This one seemed like the most likely one we could get. We found some interesting things, but never got the flag.

The registration page had quite extreme requirements on the fields. We tried changing the html code to see if we could bypass these, but the server still send back ‘incorrect password format’. I found an online string generator to create a password that was the right length and made an account. Logging in brought me to a files page.

Uploading files would indeed strip all extensions and odd characters were not allowed (space and underscore got through). Once uploaded the files would show up on the file page and clicking the links would automatically download the files.

We noticed the registration page would not allow registration of a user that was already registered, such as admin. The login page also gave away some information, returning something like ‘unknown user’ or ‘wrong password’ depending on whether the user existed or not.

Alex worked on exploiting this and ran a username list against it. He came up with valid users of ‘root’, ‘staff’ and ‘builder’. However, the port was unhappy about too much traffic and seemed to shut down if we tried brute forcing too much. So we figured that wasn’t the solution.

Another interesting discovery was that a logged in user could access files from another user by using the right url path /user/sarah/files/filename. This resulted in either the file, or a not found message. Trying to access another user’s files page directly resulted in access denied. Alex enumerated through some users and file names, but the port was unhappy, and booted us when we tried very much of that.

Port 15122  – SSH (Unsolved)

The ssh black box. Trying to connect resulted in a password prompt. We tried guessing the password, tried different users, etc.

ssh -p 15122
kali@'s password: 
Permission denied, please try again.

Eventually I looked up how to brute force an ssh connection. It took me a long time to get the tools running and finding the wordlists and things on the kali box was a challenge for me (remember non-programmer here). Eventually I got medusa working and tried a password list against the connection.

medusa -h -u root -P /usr/share/seclists/Passwords/darkweb2017-top10000.txt -M ssh -n 20123 -f

Unfortunately the port shut me down which had me assuming that brute force was the wrong approach. I did try some more password lists later and eventually tried a username list too with -U. Medusa zoomed through a lot of them, but then the port would close up again.

I tried a verbose ssh command (ssh -v -p 15122) which resulted in all kinds of information, but nothing that seemed useful. I also tried an ssh-audit (ssh-audit -p 15122) which gave me some more information, but again nothing that seemed particularly useful.

Port 20000 – Click Fast: 2 of Clubs

A webpage with a download.

Downloading and unzipping resulted in a folder full of files, including an executable called clickracer. Unfortunately it wouldn’t open on my mac. Alex took a look and tried it in a virtual machine, that was painfully slow, so he made a bootable flashdrive and ran kali linux directly.

The game connected to a port on the target machine, port 20001! After connecting to wifi on kali and setting up a tunnel, it was time to click. The game had four modes: Easy Practice, Easy Challenge, Hard Practice and Hard Challenge. Red dots would appear in the window and the user would have to click them. The practice setting gave the user 30 seconds, and challenge was 5 seconds. In hard mode the targets were invisible!

Alright, time to design a program to play the game. This one is all Alex. He used Wireshark to capture the communications between the game and the port. Luckily it seemed to be in plain text. Starting with the easy mode, he wrote a program that would ‘click’ at the exact coordinates sent by the port. After a little debugging, it worked. Playing both practice and challenge mode resulted in a flag.

So… if easy mode gave us a flag, does that mean there is another flag for hard?

Sure enough there is, but the communications captured by Wireshark are not text! This time there seems to be code, or a protocol. Using the previous challenge as reference we eventually figured out which packets were what types of messages. Then Alex just needed to change his program to send the right coded messages back. Having a Wireshark capture with no clicks, and a capture with clicks helped to narrow things down. Once he had everything working it was just a matter of ‘playing’ the hard practice and hard challenge games to get the flag.

Alex’s Notes

This was a fun one. The “easy” challenge had a few different messages that would be send across the wire, and they were all JSON encoded. To solve it, every time the server sent a TargetCreated message with x and y coordinates, I would send back a ClientClick message with the same coordinates, and we would win!

For the “hard” challenge, all of the same packets were being sent, but using some kind of binary protocol. I never did figure out what the protocol was, but after staring at it for long enough, we eventually figured out what data mapped to what, and basically did the same thing as for the “easy” challenge. The biggest difference was that I needed to decode and encode the binary packets in order to communicate with the server, which took some messing around in code. But eventually we got it to work.

Port 20001 – Click Fast Server: Black Joker

Not a website. When I hit it with netcat I got a strange message about game mode.

nc 20001


 PxPyMissing game mode

This is related to the above port 20000 – Click Fast. The game connects to this port and communicates with it. Alex wrote a program to connect to the port and play the game.

Port 20011 – Gallery (Unsolved)

This challenge was a lesson to us. It didn’t show up in our initial nmap scan and we didn’t find out about it until the last half hour of the competition! We think the target machine wasn’t quite finished initializing when we ran nmap against it, or something? We didn’t even question it when we saw 18 ports and 18 challenges. At any rate it was pretty disappointing to find out we missed a port (again!).

Port 20022 – Troll image: Jack of Hearts

Oh joy, a troll with cool shades. This was what welcomed us when we tunnelled to this site. Oddly enough it was called challenge.php. The web browser figured out it was an image and displayed it properly.

There didn’t appear to be a lot to it, the website was very simple. We did the usual checks of headers, nmap output and cookies. Ah cookies, seems to be the theme this year!

The Cookie was double-Base64-encoded (figured out with cyberchef) and had a serialized PHP object of class “user” and fields:

object(user)#3 (3) {
  string(5) "guest"
  string(23) "/var/www/html/guest.png"

Tried setting “admin” to “true”, and got this message from the server: “Did you think it was going to be that easy? Right idea, wrong approach :)”

Each try resulted in another message encouraging us to try harder, it even told us where the flag was, under / directory as flag.png. Well perhaps we could change the cookie to serve up the flag as the “profile_img”. Nope, won’t let us read directly.

Okay, how about directory traversal in the url, can we trick it that way?


Yes! It got us the flag! No more nasty troll.

Port 20055 – Secure File Upload: 9 of Spades

Hmmm, an upload page with code! There was also a hint at the bottom: “Note the flag is located at /flag.png”

This was a tricky one. It seems like having the code should make it easier, but that isn’t always the case. We could walk through and see what it was doing. Setting up a storage path, making the file extension lowercase and then comparing it to a blacklist of forbidden extensions. If we could get a php file up there, the server should run it when we navigate to it.

When we tried with a test file, we found a hint. “File has been uploaded successfully and is now available here! But can you figure out how to execute it?” So getting the nasty file uploaded is only part of the problem, execution could also be an issue. The server would only run it if the file has the right extension. We could upload .php disguised as a .png, but it wouldn’t run.

After a lot of looking, thinking and reading about functions, Alex figured it out.

Alex’s Notes

I loved this one. Took quite a while to figure it out. I was actually reading about how Apache chooses what files to execute as PHP code, when I found an article explaining how to set up custom filename extensions as PHP files in the .htaccess file. Then it clicked! If I could get a .htaccess file into the file_uploads directory, then I could make any file extension “executable” as a PHP file. This was the .htaccess file I uploaded:

Addhandler application/x-httpd-php .html .php .myext

And then uploaded a file evil.myext with the following code:


echo 'PWNED';

copy( "/flag.png", dirname( __FILE__ ) . "/flag.png" );

Then we could download the flag!

Interestingly, this one is actually a full RCE vulnerability, so we could have popped a shell and dug around on the server more if we had wanted to. If we thought there might be another flag on there somewhere, then that would have been a good next step.

Port 20123 – SSH: 8 of Clubs

Another SSH port, but luckily this one gives us credentials to get in. Once inside Alex took over. Essentially he found an encrypted flag and a Python program to encrypt the flag. It only required a little reverse engineering before he figured out how to modify the program to decrypt the flag instead. Then it was a matter of getting the flag out intact by converting to hex, sending it through the connection and changing it back on the other end.

Alex’s Notes

This one was a little bit weird. Basically, it gave me a Python program which had code to encrypt a file, and a file called encrypted_flag. It used a funny encryption library called Fernet. It’s a symmetric encryption algorithm, and by inspecting the code I could see that the key could be recreated. It looked like it was using random numbers, but it was also seeding it at the beginning so that the “random” numbers were the same every time.

I copied the file and changed the call from encrypt to decrypt. Something didn’t work on the first try, so I messed around with it for a while. But eventually it worked. I’m not quite sure what I was doing wrong initially, but eventually we got the flag.

Port 30033 – ELF Challenge code

Also not a website? Connecting using netcat: nc 30033 didn’t do anything initially, but pressing enter results in [error] invalid input key.

Alex got it! Required a huge amount of work to reverse-engineer the ELF file. See the next port for more information.

Port 30034 – ELF and pip: 3 of Clubs

A simple website with a couple files to download.

The challenge file was an ELF. First I tried to find the word ‘flag’ in the file, sure enough ‘Flag.png’ and ‘the flag file was not found’ were both present in plain text. The Pipfile was very small, probably a support file. We never got very far with that. The challenge program was interesting though, and worth digging into.

Alex loaded the challenge program into Ghidra and worked away at reverse engineering. At one point he came across an error that said [error] invalid input key. It seemed very familiar, but I couldn’t remember why. Finally we realized that the code he was dissecting was running on port 30033! We needed to send the right ‘key’ and it would send back the flag.

Unfortunately there were a lot of functions calling functions and reversing a ‘key’ was looking very difficult indeed. Eventually Alex had a eureka moment and shouted out “It’s a program!”. I was rather confused because, duh, of course it’s a program. No, it was expecting a program as an input. The functions were doing math, pushing bits on and off a stack, doing multiplication and division! Alex needed to write a program with commands to calculate the right ‘key’.

After much fiddling he knew what the result needed to look like, but how to use the commands in the program to come up with that result? Involved a bit of math and head scratching. Luckily he could test it all locally and walk through the challenge program in Ghidra to see if it was working properly. Eventually he got a program that worked. He sent it to the port, and it spit out a stream of text that was the flag png.

Alex’s Notes

This one took quite a bit of time, but was very rewarding in the end. Sarah and I analyzed the first function together, and it took the longest by far. We figured out that it was doing stuff with a data structure, and eventually figured out that it was a stack. It also had some info in a header. We figured out that the function we were analyzing was the pop function for the stack.

The next function was basically a switch based on the next byte in the input key. That’s when it clicked for me. The key was a program, the function was switching on the next byte (the opcode), and then it was calling other functions which did the actual work. Turns out if the number in the key was positive, it was just being pushed onto the stack, and if it was negative it was being used as an opcode. The operations were multiply, divide, left shift, and right shift.

In order to make it print the flag, it needed the first two bytes to be specific values, the last two bytes to be specific values, the program to be a specific length, and the result to be a specific number. Since we couldn’t add or subtract, it took some messing around to come up with multiply and divide operations that would work out. But we did, and we got the flag!

#!/usr/bin/env ruby

DIVIDE   = 0xe0
LSHIFT   = 0xc0
RSHIFT   = 0xc1
FINISH   = 0xff

program = [


print program.pack( 'c*' )

Port 33337 – Chinese Website: 3 of hearts (Unsolved)

A website with an error page in Chinese. It automatically redirected us to which gave us the card name. Translating the page didn’t give much info.

The nmap gave me a little more to go on. What is Apache Traffic Server? Phew, a complicated beast that is what. The version number is outdated, but I couldn’t find an obvious exploit. There were some vulnerabilities, but nothing that I thought was the right track. There was nginx in there too, because the 404 page on the website mentioned it.

33337/tcp open  http-proxy Apache Traffic Server 7.1.1
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: ATS/7.1.1
|_http-title: Did not follow redirect to

Didn’t get very far with this. Being a higher port number meant it was probably a harder challenge, so we focused on the others instead.

Port 35000 – Pcap: Ace of Diamonds (Unsolved)

A website with a message and download.

Late in the competition I downloaded Wireshark and took a look at the pcap. As I walked through I noticed some messages in the packets.

  • What does this protocol use to align fields?
  • A lot of things can happen when structures are not properly aligned.
  • But wait… is the actual value matter?
  • Not too much to find here… just regular backups.
  • The content is not that useful as it looks like.
  • Barnier Gauthiot Gilbertus Kochiu Hippolytos della Corte

The pcap appears to be three repetitions of a similar stream of events. It seems to be backing up notes, photos (a few big data dumps), emails, etc. It would be worth trying to extract those ‘photos’ to see if we can decode them and see if one is a flag.

Tried to get Wireshark to extract the files for me, but either I was doing something wrong, or the pcap is formatted in a way to obfuscate the files. Maybe that is what the message means regarding aligning fields.

Alex took a quick look at this too. He understood it a little better and looked up documentation on SMB, it was immense. Aligning fields sounded complicated and confusing, nothing we could tackle in the time we had left.