Python Playground - TryHackMe Writeup
Nmap exposes SSH and a Node.js/Express app on port 80. Gobuster finds an admin.html login that only exists client-side - the real login and signup pages are dead stubs.
Reversed the double base-26 encoding of the client-side password, reached the hidden Python sandbox, and slipped past its substring filter with __import__('subprocess').getoutput() - the interpreter runs as root inside a docker container.
Reused the backdoor credentials over SSH into the docker host, then reproduced the Baron Samedit sudo heap overflow in pure Python to spawn a root shell and finish the machine.
Introduction
Python Playground is a hard-rated TryHackMe machine created by deltatemporal that explores client-side cryptanalysis, web application filter evasion, container isolation mechanics, and Linux privilege escalation. The target hosts a Node.js/Express web application providing a web-based Python execution environment with a self-proclaimed "foolproof" blacklist filter.
The exploitation journey spans three distinct phases across the container and host:
- ▹Client-Side Backdoor Reversing: Analyzing Connor's client-side login validation logic to invert a double base-26 letter-pair transformation and recover valid credentials.
- ▹Sandbox Blacklist Escape: Evading the substring filter via
__import__to execute arbitrary commands as root inside the Docker web container (flag1.txt). - ▹Lateral Movement & Host Exploitation: Reusing Connor's credentials over SSH against the Docker host (
flag2.txt), followed by executing a pure-Python Baron Samedit (CVE-2021-3156) heap overflow against outdatedsudoto achieve full root compromise on the host (flag3.txt).
Reconnaissance
::Nmap Scan
A full TCP scan kills it down to two services:
Only SSH and HTTP are exposed. Everything interesting - the docker engine, the python web unit, and the third flag - lives behind the frontend, so we have to earn our way in through the web application.
::The "Secure" Homepage
The landing page advertises a sandbox that keeps hackers out with a "foolproof blacklist":

The Login and Sign up links both lead to dead stub pages - the developers moved the real product "behind admin":

::Directory Fuzzing
A quick pass with Gobuster points at the page that still ships real markup:
Initial Foothold
::Connor's Secret Admin Backdoor
admin.html is a login form titled "Connor's Secret Admin Backdoor", and its comment set the tone for the entire room:

The validation is entirely client-side. The username is compared against a literal, and the password is double-encoded before being checked against a hardcoded digest:
::Inverting the Encoding
Each character becomes a pair of lowercase letters - one for ord(c)//26, one for ord(c)%26, both shifted into the a..z alphabet - and that transform is applied twice. Inverting it is just character subtraction:
Credentials: connor : spaghetti1245 - and the digest trade reveals the hidden page: super-secret-admin-testing-panel.html.
::
Correct credentials drop us into Python Playground! - By Connor, a source-level code runner:

A benign run confirms both execution and output reflection:

The moment the interpreter wants something interesting, the wall appears:

Sandbox Escape
::Mapping the Filter
Fingerprinting which strings trip the filter shows that import (with the trailing space), eval, and exec are banned - while __import__ (no space) runs fine. We can read the exact filter source directly from the host by abusing the same sandbox with __import__('subprocess'):
The filter is a set of substring checks against the raw request body, so the bypass is literally encoded in its own gaps:
| Banned substring | Working equivalent | Why it slips through |
|---|---|---|
import | __import__(...) | __import__ has no import |
.system | subprocess.getoutput(...) | no .system substring anywhere |
exec / eval | (not needed) | getoutput already gives shells |
And note what the runner does with the code - it writes the file itself into the app directory before spawning python3 on it:
::Command Execution at Root
With the filter understood, honest command execution is one line:

The python interpreter spawned by the Node app runs as root - no container-user sacrifice here, which is why the "mid-tier" flag is readable straight away. Any reverse-shell payload you would usually stunt through the sandbox is entirely unnecessary for the first flag, but pivoting out of the container still needs a second account on the host.
Pivoting to the Host
::Exploring the Container Network
Running getoutput against the container's network view shows only one peer - the docker gateway - and our own internal IP:

playgroundweb is a docker container on 172.17.0.0/16, and 172.17.0.1 (which serves SSH and
HTTP on the published ports) is the host machine. The room's remaining two flags live over
there.
::Password Reuse over SSH
Connor ships an identical password to the web backend, so we aim it straight at the host:
Privilege Escalation
::Washing Away sudo
connor can't leverage sudo at all - sudoers greps back empty for the user:
No docker group membership either, so the classic socket isn't reachable:
The host is running Ubuntu 18.04 (kernel 4.15.0-99) with a vulnerable sudo:
sudo 1.8.21p2 falls inside the affected range of CVE-2021-3156 (Baron Samedit) - a heap overflow in the argument-parsing code that any user can trigger via sudoedit -s before authentication is even attempted, no sudo rights of any kind required.
::Pure-Python Baron Samedit
The host has no gcc, so we drive the exploit with a pure-Python port of Sleepya's service_user overwrite technique (see worawit/CVE-2021-3156), delivered over SSH with paramiko. The exploit overflows sudo's heap, overwrites the NSS service_user struct so glibc loads our libnss_X/X1234.so.2, and that library spawns system("/bin/sh") as root:
Summary
Python Playground is a demonstration of what happens when every layer trusts a string:
- ▹A client-side login published credentials inside an invertible base-26 digest - password never knew the server.
- ▹A substring blacklist guarding a Python runner fell to
__import__because the blocked token included its own trailing space. - ▹The python interpreter ran as root inside the container, opening the docker gateway and a host account with password reuse.
- ▹An unpatched sudo (1.8.21p2) on outdated Ubuntu left the whole host one heap overflow away from root, with no sudo membership needed by the attacker.
Automation
This whole chain was scripted end to end - client-side decode, sandbox probe, filter read, container root, host SSH, and the Baron Samedit delivery and detonation:
Standalone Solver Script: View or download the complete automated Python exploit
thm_pythonplayground_solve.py
in our /autosolve directory.
References

Red Team Consultant · Penetration Tester · Bug Bounty Hunter
Offensive security professional with 250+ vulnerabilities reported across 50+ organizations including Atlassian, Vimeo, and AT&T. Sharing research, tools, and field notes.