This past weekend I organized a team of coworkers for a little time… ok a lot of time… team building and fun with technical hacking challenges. I’ve enjoyed past editions and looked forward to this one. They feel more real world than some of the other CTFs I’ve participated in the past.
Post-CTF write-ups are great to know what little thing that might have been missed. So here are a few of the ones I was able to solve. Rest assured I’ll be searching for write-ups for those challenges that escaped us too.
Note that these challenges required SSH tunneling into a provisioned Kali system, as a result many of the web requests below reference
The SETUID Ace of Clubs (port 9009)
This challenge started with an unknown login to a server.
Login as admin toget started. Turns out, everyone’s favorite admin/password worked. Not tricks, I just guessed. Once logged in, I looked around manually for anything interesting. Later i found some useful CTF and Linux escalation scripts that will help in the future. I soon found that we have an
ace_of_clubs.png in the
/etc folder, and in interesting
vpn_connect executable in the
vpn_connect program has
rwsr-xr-x permissions, and I skipped over the
s at first, but that key bit is the
SETUID bit that means the program executes with its owner’s privileges — root. The program accepts 3 arguments: username, password, and log file location. With the
SETUID bit, this means it can touch ANY file on the system. What’s more, the created log files therefore write their log at the start of any file you point them at. I started trying to write commands into logs through the username field and executing them like so:
As you can see, this reverse shell is only running as our current user since I had to execute it myself. So any escalation or execution has to be done using
vpn_connect. At this point I broke out the local
gdb debugger to walk the code, try to find the built-in username/password, and see if there are any buffer overflow vulnerabilities that would let us execute code as root. I spent hours here stepping through execution to pull out
username/securePass only to see it doesn’t actually do anything and all string variables are properly handled. If I had only remembered Ghidra’s amazing decompilation capabilities it would have saved me all that time.
With no executable exploitation found it’s back to leveraging the
SETUID bit. Lots of searching for ways to use the
SETUID rights to escalate. I tried changing my
PATH variable to point at
/tmp/umask and hoping the reference would be executed only to realize my
PATH variable had to impact on the
PATH for root. Eventually I found a reference to overwrite
/etc/passwd with new credentials and group membership to give us root privileges.
And it works!
SpyHunter — Eight of Diamonds (port 5555)
Switching gears. This one is a type of challenge I’ve always wanted to take the time to solve: game automation. When you connect to the port you’re entered into what reminds me of SpyHunter — a driving game where you have to dodge other cars. If your
^ car is hit the game is over and the server tells you
you're not as fast as a computer!
A quick search for tools that would help in building a socket based program and I came across Pwntools.
Using the pwntools python socket library I wrote a quick script to read the gameboard as the server sends it, store the 2 lines above our car, check for a
0 obstacle in front of us, and send a move command based on relative position.
As you see, if the space in the line above the car
prev[pos] is a
0 (or code
48) it prints
move! for effect then decides to move left or right. I found that late in the sequence we may encounter a
00, two adjacent obstacles, so I added additional checks for that too.
I was ready to add some more logic to check lines above the immediate next line, but it wasn’t needed.
It was so rewarding and amazing to watch the self-driving car fly through the late stages of the game to victory.
Password Functions — 4 of Clubs
This will be a short write-up, but it took me a long time. Everything you need for the challenge is right here:
Take a bit and see if you can spot the way to login…
I’m sure some of you thought of it quickly, but I spent a lot of time digging into php manuals on
password_hash and its arguments. Like how
BCRYPT and if you give it a passsword over 72 characters anything beyond that is truncated. I tried
POSTing a hash with wlidcards, guessing password salts like
Clover Tail Login Page which is 22 characters, the required length of a salt, etc.
So we can’t brute force the salt, and even if we know the password and the algorithm that unknown salt will prevent us from ever guessing the right hash.
I was on the right track when I looked up source-code for
password_hash to see if it ever returns
False or errors out. In the code there’s no returning
False, and only exception clauses. Eventually I found that we could pass
password as an array by sending
password= as a
POST parameter instead of
Game Review — 2 of Spades
This was a straight forward SQL injection challenge with a twist in that the database is SQLLite based vs the more well-known MySQL exploit paths.
At least for this write-up it can be shown through straight screenshots. The infamous
' or 1==1;-- gets us started and just prints out a list of all games. Then trying to enumerate the database table structure through a traditional MySQL approach of
' or SELECT * FROM information_schema.tables;--throws an error. An we can see the database is SQLite3 based, which doesn’t have the same schema tables.
Let’s go back to enumerating number of columns and where we can get them to show up in our returned data.
' AND 1=2 UNION SELECT 1,2,3; — It looks like columns 2 and 3 are our ticket.
Now we can use a SQLite based query, which took some searching and trial and error, to figure out what tables are in this database
' AND 1=2 UNION SELECT 1,group_concat(name),3 from sqlite_master;--
hidden stands out for sure! Another call guessing at a field value of
id to confirm we can get at the data
' AND 1=2 UNION SELECT 1,group_concat(id),3 from hidden;--
Great! Now let’s grab all the columns from hidden!
group_concat() didn’t like that. Ok, taking that out of the picture with
' AND 1=2 UNION SELECT * from hidden;-- and we have the entry with the URL for our flag!
Moose Gallery — 6 of Diamonds
File upload bypass and web shell
This one was an interesting exercise in bypassing a somewhat restrictive file upload form.
A quick look at the code for the gallery and we find that the
gallery.php page links to each of the referenced images, which the client’s browser then loads through individual requests. This is an important point because it means that my early attempts at pushing files that had reverse shells in them only proved to reverse myself.
Of course we immediately find that the file upload is restricted to only a few image formats, and it’s also doing some amount of content validation. Typically you would try
- Send a web shell like
<?php system( $_GET['cmd']); ?>as content of the uploaded file, and name the file
something.jpg.php. This double-extension filename often gets by basic filename filtering as it sees the first
., and ignores the second extension. If successful, and the site isn’t looking at file content it would give us a shell we could do any thing with through requests to the page such as
/page.php?cmd=ls← obviously what we want to happen here! No joy.
- The next level is to use a simple GIF image header
GIF89a1in front of your php shell payload, but that didn’t work either.
- Exif content fields in jpg images are known to work as well. Let’s go that route.
During attempts 1&2 I found that the
upload.php page was definitely vulnerable to cross-site scripting (XSS) by adding a
\ character in the filename, and totally jacked up the page a couple times in trial and error hoping for in-page PHP code rendering by the server.
Exif-based shells have been around for awhile. Really interesting reading:
exiftool we add a comment to a random jpg file we are able to upload it, but attempts to reference the shell threw errors on execution. With some troubleshooting I found that by removing everything after the PHP shell still passed the filter test and still worked.
This is not the moose you’re looking for!
And while the page doesn’t load here, we can reference the image directly with our
And here we go!
Black Joker — I Hate Salt Password Cracking
This was a fun test of observation and having access to a password cracking setup. It probably wouldn’t take much time for even an underpowered system to solve this one.
So here’s our entry page:
The create account page tells us that new accounts aren’t being accepted right now. And the source code has embedded regex check to make sure the submitted password is between 9 and 14 characters using only lower-case
0-9. That’s a pretty small keyspace outside of the passwor length.
Knowing the admin from our front page we can ask for a password hint:
Isn’t that special! We have the first 9 of a potential 14 character password… but that’d still be a lot of requests to throw at this webpage. Fortunately, a closer inspection of that password hint reveals something else:
The password hash for the admin user was included in the JSON response! Armed with the hash, the initial 9 characters of the password, and a password scheme it’s off to create a hashcat ruleset for this password. I made a file listing out each of the characters as a rule option, and then set it in motion:
hashcat 8123.txt -wordlist ./wordlist.txt -r addon.rule -r addon.rule -r addon.rule -r addon.rule -r addon.rule
With the password in hand we go to the easily missed Admin login link at the bottom of the main page, an we enter our newly found creds into the Basic login prompt
Subdomain Brute-Force — 9 of Diamonds
This challenge took alittle Burp guesswork and Host header configuration. When you visit the initial page by IP address you get a redirection to
intranet.metasploit.ctf which of course would fail in our browser as we don’t have an entry for that.
It also tells you that the intranet page is still under construction, and to use the direct subdomains already sent to you to connect.
There aren’t any obvious other ports open around this one, and what few tools are out there for enumerating DNS sub-domains are intended for open internet usage, not this challenge situatoin.
So I’m thinking this server is also serving the subdomain(s) pages. However, in order for the server to answer we have to make a request for the name not using just an IP as we have for all the other challenges.
Alrighty then, with an entry in our hosts table we can send a request to
The next step is to use Burp’s great Intruder module to mark where we want to fuzz some input on web requests and see what the server gives us in return. Burp can certainly make short work of a subdomain list.
I let it run sorted on Status response, and the game masters were nice enough to give a carrot to the players with
assets.metasploit.ctf being valid. The page said
Good job, but this isn't the right site! or similar. So we knew we just needed to let it run it’s course and soon
hidden jumped out with this response:
At this point we could download the flag directly from our Kali system which has the
curl -H 'hidden.metasploit.ctf http://intranet.metasploit.ctf:8123/9_of_diamonds.png
Answer Guessing — 3 of Spades
This was one of the first ones solved, but it’s a good example of how quickly brute forced queries can find things. Security through obscurity does not work.
Standard login page tells us that
guest is a valid username, and we need to guess at another.
Looking at the page source we see it’s a PHP posting form.
Capturing the request our browser sends out shows it’s a basic query.
Let’s automate these queries with a word list through Burp Suite:
After awhile we hit paydirt:
demo returned a successful result:
We can now grab the flag!
Thanks for reading. I learned a ton about various techniques, and if nothing else I hope to use this write-up in the future to help remember what worked for me in previous CTFs.