TryHackMe Python Playground—Writeup
Welcome back to another write-up! In this post, I’ll walk you through how I tackled the Python Playground room on TryHackMe, breaking down my thought process, mistakes, and key takeaways along the way. It feels great to be writing write-ups again after being tied up with a lot of stuff lately, but I’m finally back in the grind.
This challenge was a fun mix of logic, Python quirks, and problem-solving—perfect for sharpening both scripting and analytical skills. Whether you’re attempting this room for the first time or just want to compare approaches, I hope this write-up helps you learn something useful and maybe even think a little differently.

Reconnaissance
First, we’ll scan the target for open ports to identify possible entry points.
nmap 10.66.162.246

As shown, ports 22 (SSH) and 80 (HTTP) are currently open.
Enumeration
Now let's visit the webpage.

There’s nothing noteworthy here, so let’s attempt to sign up.

Only admins? When we try to log in, the same message shows up.
We’ll proceed with subdirectory enumeration using Gobuster, as there’s likely more content.
gobuster dir -u http://10.66.162.246 -w /usr/share/wordlists/dirb/common.txt -q -t 20

In addition to the discovered subdomains, an admin.html page was found. Let's visit it.

It’s a login page, but since we don’t have credentials yet, let’s inspect the source code for any hidden or useful information.

We found a set of credentials here, but the password is hashed. There’s also a hidden path, so let’s check that next.

From the looks of it, this appears to be an online Python IDE. Let's try to run some code here.

As you can see, it’s working! Next, let’s make some adjustments and see if we can import modules.

As expected, certain operations like import are restricted. This is similar to what I encountered in the PyLington Boot2Root challenge on VulnHub.
To bypass the restriction, we can take advantage of Python’s built‑in __import__ function, which is what the interpreter internally uses to load modules. In many sandboxed environments, the import keyword is blocked through simple keyword filtering, but the underlying functionality remains accessible. By directly invoking __import__, we can load the necessary modules without using the restricted keyword, effectively bypassing the limitation, an approach commonly seen in insecure Python sandboxes.
Before we execute this we must set a listener via netcat .
nc -lnvp 4444
Now let's execute our payload.

We’ve successfully obtained a shell, and as shown, it’s running with root privileges. While exploring the system, we were able to retrieve flag1.txt .

Next, we’ll look for the second flag. From the earlier Nmap scan, SSH was open, which suggests that connor might be a valid user. Let’s revisit the login page source code we examined earlier.
What JavaScript encoder does?
The encoder is composed of two simple reversible functions, applied twice.
1. string_to_int_array(str)
For a character c:
This is just base‑26 decomposition.
Mathematically:
Where:
partA= quotientpartB= remainder0 ≤ partB < 26
So no information is lost.
Example
Character: 'a'
2. int_array_to_text(int_array)
This maps numbers → letters:
Meaning:
So [3,19] → "dt"
One Full Encoding Pass
For one character c:
Like this:
This operation is bijective (1‑to‑1 and reversible).
Why It’s Applied Twice?
The JavaScript code does this:
So the flow is:
Each character:
This is why the hash is so long. (Well, obviously not a hash since it's reversible-,-)
Here's the decoder I've made to reverse this using Python:
Run the script with the "hash" and here's what we get:

Next, let’s attempt to log in to SSH as connor using the password we obtained.
ssh connor@10.66.162.246

And we've found the flag2!

It’s time to go after the final flag. Initially, I struggled, assuming there was a privilege‑escalation path from the connor user, but after returning to the reverse shell I noticed the content of this directory /mnt/log .

A closer look shows that this directory contains logs tied to activity from the system accessed using Connor’s account. Because we already have root access on this machine, we can abuse this trust boundary by placing or modifying files in a location Connor can execute. When a root‑owned file with unsafe permissions is exposed through shared or improperly protected paths, a normal user can run it and inherit elevated privileges—demonstrating how poor isolation and log directory misuse can lead directly to privilege escalation.
Here's how we can exploit it
In our reverse shell:
cp /bin/sh /mnt/log
This copies the system shell binary into a directory that is accessible from the logging path.
chmod +s /mnt/log/sh
This sets the SUID bit on the copied shell, forcing it to run with the file owner’s privileges (root) regardless of who executes it.

Now that it's all set, let's go back to connor and execute this command:
/var/log/sh -p
This executes the SUID shell while preserving elevated privileges, resulting in a root shell for the unprivileged user.

We're finally root!! And we've successfully got the flag3!

We've successfully solved Python Playground!

I hope you learned something from this write‑up. More importantly, don’t fall into the habit of blindly reading write‑ups and copy‑pasting commands—you’re only fooling yourself. Real learning happens when you understand why an exploit works, not just how to run it. Take the time to analyze, break things, and think critically, because that mindset is what actually makes you better.
Last updated