This isn’t intended as a “full” walkthrough, I’m basically just going to outline my approach and perhaps mention a few alternatives.
A quick note; this is the first time I’ve participated to any major degree in a CTF contest (Though I have tested and designed a number of levels for the Ruxcon CTF over the past couple of years), I’ve poked at a few in the past but I seem to quickly lose interest. I think the fact that this one was over a defined time period and that the #ctf-support channel was there on slack really helped to keep me engaged.
Recon and initial access
Of course the first tool I reached for out of the gate was nmap, I wasn’t particularly discrete about it because I figured with the objective of this one there wasn’t likely to be any network-level mitigations in place.
I pulled that into metasploit by way of db_import, which gave me the following to work from;
Which provided us with quite a few options as far as exploitation goes, but first off the bat I did some sniffing around the various httpd services that were lying around to see if there was anything interesting, at this point I found;
- phpMyAdmin, drupal, a custom “payroll_app.php” and a custom chatbot running out of apache.
- A Rails server running on 3500.
- A custom web app running on 8181.
On to exploitation, had a poke around the exploit database in Metasploit and settled on ‘exploit/unix/irc/unreal_ircd_3281_backdoor’.
This got me a shell on the box as boba_fett, which was enough to get me several of the flag source files, and also lead me to creds for the rest of them.
While I was looking around the system I found a PoC for an SQLi in the payroll_app (Equally could’ve gone direct to the database since the credentials were in the clear in the file, but if it’s there you might as well use it :) ), I’d previously confirmed that it was likely vulnerable to SQLi but parked it for later.
This got me a dump of user creds for the Payroll App which I then fed through ‘auxiliary/scanner/ssh/ssh_login’ for validation.
Turns out most of the users on this system (with the notable exception of darth_vader) are guilty of re-using passwords, this got me a bunch of low-privilege user accounts I could directly login with so I could at least switch to a “real” interactive shell ;)
8 of Clubs
This was a quick and easy find due to world-readable home directories, find doesn’t care about depth of directory structures, so;
quickly turned this and others up;
This one was a very simple, “get file, md5sum file, submit hash”.
9 of Diamonds
This one was found by manually hunting through home directories, it was found in a hidden folder under kylo_ren’s home directory;
The flag was then retrieved by mounting the iso and doing the usual “md5sum file, submit hash” dance.
2 of Spades
This one was also found from the above “find”;
This one requires a little bit more work to retrieve, scanning through the pcap in Wireshark, we see a bunch of local network traffic which is not terribly interesting, but as you dig further you start to see this;
So we have a VoIP call, so let’s see what Wireshark has to say about that (Telephony->VoIP Calls);
Playing back the call we find that it’s a very scratchy audio call to an echo test service with a voice reading out a URL for us; https://imgur.com/gmThKFP going to the URL, sure enough, there it is, the 2 of spades.
10 of Spades
This one was another that was found by find, I don’t recall specifically, I think it was probably on /opt.
Another retrieve flag (/opt/readme_app/public/images/10_of_spades.png), md5sum file, submit hash.
5 of Hearts
Was one I’d found earlier, but hadn’t looked into in detail yet, it was openly available on the Drupal install on Apache, but the file wasn’t quite right (each challenge described the flag image);
So I parked it while I went after any other low-hanging fruit.
Once I got back to this one I fed it to identify from ImageMagick, and was greeted by a blob of base64 encoded data in a tEXt chunk in the file, the text was duly extracted and fed to base64 -d to decode it into the original binary, which was the PNG I was looking for;
3 of Hearts
It was about here that I started looking for root, the rest of the flags had been acquired with low-privilege accounts.
It took longer than I really care to admit for me to figure out how to escalate to root, I spent a couple of hours slaving over the process list looking for convenient chinks in processes run by root. Classic overthinking though, because at some point I did a “cat /etc/group” and immediately facepalmed when I found this;
Two of these are accounts I had credentials for, so I switched to han_solo asked “I wonder” and ran “sudo -s” lo and behold, unrestricted root shell. One facepalm later, I continued on with the process.
First port of call was to re-check for low-hanging fruit in case there was something which wasn’t world/users readable, there was;
Winner! Another quick md5sum file, submit hash.
5 of Diamonds
Onward and upward, earlier I’d noticed in the process list that knockd was running, but until I got root there wasn’t anything I could do with that because /etc/knockd.conf was owned by root and had 0600 permissions, now I could actually see it.
Back to the Kali box, install knockd, and;
Quick check with nmap;
We’re golden, hit it with lynx, download the file it presents, do the dance, and done;
7 of Diamonds
The source file for this one was also discovered in the earlier global find as root, it was an encrypted ZIP file sitting in a docker container, it was duly retrieved and inside it were found; “hint.gif” and another “7_of_diamonds.zip”.
Excuse the animated gif.
So it’s an animated gif with a stack of QR codes in it, so presumably we need to extract the frames, read the QR codes and concatenate all the data together.
I used an online service to split the gif into frames (could’ve used ImageMagick but I’d have had to read the manual to remember how to do this operation ;) ), then wrote some code around the ZXing library to parse each of the frames in turn (I went hunting for some pre-existing code but couldn’t find anything quickly), and spit out the binary data (each QR code had a hex string which was part of the larger file), which yielded this;
Which allowed me to extract the actual flag file.
8 of Hearts
I’d retrieved the source for this one pretty much immediately after getting into the box, the root credentials for MySQL were in the clear in the payroll_app.php file, I logged in to MySQL via phpMyAdmin and found the following;
The blob was duly extracted, and found to be an encrypted zip file. The reason this took me so long to resolve was basically down to using the wrong tool, I’d grabbed a current copy of John The Ripper, and fed the file to that along with several wordlists including rockyou, but I was unable to get a positive result, I resorted to pure brute force, by feeding the output of Mask Processor with a 13 character ASCII mask into JTR to try to crack the file (it’s a big keyspace but at 4.5Mc/s it’s not too bad), I left it running overnight.
The following day I still didn’t have a result, but I happened to have a look at the #ctf-support channel scrollback and found suggestions that the password WAS in fact in rockyou, some time later I saw reference to fcrackzip which is not a tool I’d used, so I killed my MP/JTR job and instead went for fcrackzip+rockyou, the password was found in about 30 seconds…I thought requiring an outright brute force was a bit OTT but couldn’t think of another option, live and learn.
Turns out the password was ‘vagrant’, extracted ZIP, did the dance and got the flag;
Ace of Clubs
This flag was hidden in the chatbot which was running out of Apache.
I gather it’s possible to get the bot to divulge the flag (it says as much, but you have to “ask the right question”, it’s also vulnerable to command injection so there’s an alternative path to root for you), but I dislike these types of challenges, never can seem to get the “right” question so instead I went straight for the source.
A bit of fossicking around in the files in /opt/chatbot lead me to two NodeJS processes in the process list;
Looking inside chat_client we find a base64 encoded blob, which upon extraction and decoding is “surprise” another PNG file;
6 of Clubs
This one is getting a bit more involved and seemed to cause the most “technical” trouble for contestants, I coached about 6 other people through solving this one one way or the other.
There are two pathways to it, either you can modify and re-sign a cookie, or you can recover the source. Of the people I coached, I think it was about a 50:50 split.
I’m not much of a web hacker so I went straight for the source, but for some variety here is the broad strokes of how to do it through the “front door”;
The web app for this one is running on 8181, when you access it it tells you what you need to do, “The /flag route can give you a flag if the _metasploitable cookie has the name of the flag.” but the cookies it sets are signed.
The cookie signing secret is in the session cookie that is set when you connect to the service, so you need to extract that, then add “6 of clubs” into the _metasploitable field within the cookie, then re-sign it and replace the cookie, it took quite a while for all the people who took this approach successfully to get the cookie “just right”.
I took the “other” approach, a bit of hunting around lead me to two ruby processes in the process list;
What we can see here is that something is changing to the directory /opt/sinatra, and executing some ruby code which picks up a hidden file ‘.raIhUJTLEMAfUW3GmynyFySPw’, reads the contents, deobfuscates it, stores it in a variable, deletes the original file then “eval”s the code to execute it. Logically something must be creating that file on startup because otherwise this service would be broken on reboot.
So we need to A) figure out under what circumstances the file is being (re)created, and B) retrieve it before it gets deleted.
Some hunting in /etc/init reveals that “sinatra” (which is referred to in the first command above) is a service on the system (it doesn’t appear in ‘service –status-all’ presumably “because systemd”).
Issuing a “service sinatra stop” we observe that the two ruby processes above have disappeared, so evidently this is the service responsible for the creation of the file and executing the code which removes the file.
Excellent, a quick shell script later;
Run the script in the background, bounce the service;
Ta Da! We have our file. From the commands above we can tell that it’s going to be obfuscated so we need to do something about that, the command above with a slight twist will do the trick;
Which gives us our cleartext Ruby code, once again the flag is in there as a base64 encoded blob, which is duly extracted, decoded, md5sum’d and submitted;
King of Spades
Another one which took longer than was reasonable to do…
In the early stages of the contest I connected to everything on the server, or almost everything… When I originally attempted to connect to the ircd on the box I ran into trouble, the IRC server was running on 6697 which caused me to assume it was using SSL, it wasn’t…
I’d gone through most of the rest of the flags before coming back to this one, and I’ll admit I needed a nudge (thanks @Fullname) to move me in the right direction, when you connect to the ircd you get the following;
Initially I logged the output, cleaned it up and fed it to ‘base64 -d’ but that didn’t work because irssi truncates the lines. Never mind, we now know that the flag is in the irc message of the day so we can extract it directly from ‘/opt/unrealircd/Unreal3.2/ircd.motd’
Content was decoded and found to be a PNG image, but hang on… This doesn’t look like the description…
Looking a bit deeper into the file with a hex editor (binwalk would’ve done it directly though) we notice that something’s not quite right here, PNG files aren’t supposed to have PKZIP trailers…
The PNG was stripped off the top of the ZIP archive and the archive was extracted revealing “the real king of spades”;
10 of Clubs
This one is another that took me WAAAAY too long to solve, I got there after a nudge from another contestant (thanks @Fullname). The problem here was basically a failure to abide pretty much the first commandment in binary file analysis “thou shalt binwalk”.
I’d spent some time manually pouring over the data in the file looking for headers and such but didn’t find any, which lead me to the rabbit hole of overthinking, I’d basically tunnel visioned onto the idea that the data must be somehow encoded into the audio, spent a lot of time with stego tools and GNU Radio Companion trying to work it out.
After a comment about “not overthinking challenges” in #ctf-support, and the aforementioned nudge I was back on the path.
In the end, this one was as simple as “binwalk -e 10_of_clubs.wav” which revealed that there was a chunk of ZLIB compressed data after the WAV file header, upon extraction we find that said ZLIB compressed data is a PNG file and the flag;
This is the only challenge within the contest that I have a gripe with, it was very difficult to get right not because it was technically challenging but because it has ONE VERY specific solution (well there was also a code-based solution but I’d expect that that was written “after the fact” to replicate the results, because the chances of “stumbling” onto the “right” code is basically nil, I’ll explain further below).
So this one looks pretty straight forward, there’s a file in /etc called “joker.png” owned by root with 0600 permissions so once you’ve got root it’s trivial, right… Not quite…
The file looks like this;
So obviously the colours are inverted, that’s easy enough to fix and Rapid7 provided the following hint; “@channel A little hint for you: The Joker is tricky; it’s easy to calculate the wrong MD5 hash. In fact, you might notice the hash changes on you (this is the Joker, after all). Pay attention to why and when the hash changes. Working with an image editing tool? Watch the metadata. Using a custom script? Focus on pixels. Good luck!” Which I interpret as “make sure that nothing except the pixels change in the file”.
I then proceeded to try about a dozen different approaches to generate a file that had ONLY the pixels inverted, I eventually settled on this chunk of Ruby;
Which resulted in the desired result;
Note that the IHDR and IEND CRCs are identical, the only difference here is in the IDAT section, to further confirm I also extracted the IDAT sections from the two files and did a direct comparison, and verified that the pixel RGB values were, in fact inverted between them.
But that didn’t work… The only conclusion I could come to at that point that although compressed data ought to be deterministic, that there must be some quirk in the LZ77 implementation in chunky_png versus whatever was used to generate the “master” image, in desperation I reached out to one of the contestants who had already solved all the challenges and I was told “all the solves I’m aware of used GIMP” (thanks @mubix)…
I’d already tried that a couple of days before, but discounted it because when I looked at the file structure it was different… But lo and behold I exported from GIMP with “Save creation time” unset, and the flag was accepted.
My problem with this one is that every tool I used to perform the changes produced a file with a different structure, which basically meant the ONLY way to solve this was to use GIMP, and considering that the file structure of the “right” file looks like this;
You can probably see why I doubt that the code which produced the “right” answer was produced without having seen the file structure first (Furthermore it used chunky_png and what I have above is pretty much the simplest case for inverting pixel colours with that.).
Overall barring that one minor gripe it was a thoroughly enjoyable competition.
I thank Rapid7/Metasploit and in particular juggernot and sinn3r for their efforts in putting the competition together and catc0n for her ongoing encouragement throughout the competition.
Also, if you want to read a WAAAAAY more comprehensive and detailed write up have a look at mubix’s (Second place in the CTF) Metasploitable 3 - A Walk-through: Linux Edition