PG — Mzeeav (OSCP Prep)
“Darkness cannot drive out darkness: only light can do that. Hate cannot drive out hate: only love can do that.” ― Martin Luther King Jr.

Let’s start with enumeration :
nmapAutomator.sh -H 192.168.166.33 -t All -o nmap_logs
PORT STATE SERVICE VERSION
22/tcp open ssh 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)
| 256 26:03:2b:f6:da:90:1d:1b:ec:8d:8f:8d:1e:7e:3d:6b (ECDSA)
|_ 256 fb:43:b2:b0:19:2f:d3:f6:bc:aa:60:67:ab:c1:af:37 (ED25519)
80/tcp open http Apache httpd 2.4.56 ((Debian))
|_http-server-header: Apache/2.4.56 (Debian)
|_http-title: MZEE-AV — Check your files
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
We only have one attack vector for now and it’s the port 80 since we do not have any credentials.
Let’s navigate to the website with our browser :

It’s nice! We have an upload functionality, but, in these vectors we need to find out where the file is uploaded to. This is the problem we need to figure out. We can check directories and folders for this :

Command Breakdown
- gobuster dir
- Runs the gobuster tool in directory brute-forcing mode.
2. -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
- Specifies the wordlist to use for the enumeration.
- In this case, it uses directory-list-2.3-medium.txt, which contains common directory names.
3. -u http://192.168.166.33
- Specifies the target URL. Here, the target is the IP address 192.168.166.33.
4. -t 42
- Sets the number of concurrent threads to 42, increasing the speed of the scan.
5. -b 400,401,403,404
- Excludes responses with HTTP status codes 400, 401, 403, and 404, which usually indicate errors or restricted access.
6. — no-error
- Suppresses output of any non-critical errors to keep the results clean.
And we have /upload & /backups directories.
The upload directory was the dead end for us. Though, backups has a zip file :

I downloaded and extracted them on my working directory :

If we cat the upload.php :
<?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);
?>
Now wtf is this ? I asked ChatGPT to understand the nature of this file :
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
This PHP code allows users to upload files to a server, but it only accepts PE (Portable Executable) files. Executable files on Windows (e.g., .exe
, .dll
) are in PE format, and every PE file is identified by the "MZ" characters at the beginning. In hexadecimal (hex) format, "MZ" appears as 4D5A.
Now, let’s break down the code step by step in a simple way:
- Getting the File Name
$filename = $_FILES[‘file’][‘name’];
The user uploads a file via an HTML form.
$_FILES[‘file’][‘name’] gets the name of the uploaded file and stores it in the $filename variable.
For example, if the user uploads malicious.exe, $filename = malicious.exe
2. Defining the File Save Locations
$tmp_location = “upload/file.tmp”;
$location = “upload/”.$filename;
- $tmp_location defines where the file will be saved temporarily: “upload/file.tmp”.
- $location defines where the file will be saved permanently: “upload/malicious.exe”.
Purpose:
- The file is first uploaded as file.tmp.
- If it’s a valid PE file, it’s saved with its original name.
3. Moving the File to the Temporary Location
move_uploaded_file($_FILES[‘file’][‘tmp_name’], $tmp_location);
- The move_uploaded_file() function moves the file from the server’s temporary folder ($_FILES[‘file’][‘tmp_name’]) to the upload/file.tmp file.
- After this step, the file is now on the server but not permanently saved yet.
4. Reading the First 2 Bytes (Magic Bytes)
$F = fopen($tmp_location, “r”);
$magic = fread($F, 2);
fclose($F);
- The fopen() function opens the file in read mode (“r”).
- The fread($F, 2) command reads the first 2 bytes of the file and stores them in the $magic variable.
- The fclose() function closes the file.
Why the First 2 Bytes?
- PE files start with the “MZ” characters.
- Computers read files in bytes, and each character equals one byte.
- “MZ” = 2 bytes, so checking the first 2 bytes is enough.
📌 What Does “MZ” Mean?
- The “MZ” signature refers to Mark Zbikowski, a Microsoft engineer.
- In the 1980s, while designing DOS executable files, he added his initials at the start of every executable file.
- These initials indicate that the file is a PE (Portable Executable) file.
5. Converting the Bytes to Hex Format
$magicbytes = strtoupper(substr(bin2hex($magic), 0, 4));
The purpose of this line is to convert the “MZ” characters into hexadecimal format. Here’s how it works:
#1. bin2hex($magic)
- Converts the first two bytes of the file into hexadecimal format.
- “MZ” → 4d5a
- M = 4D, Z = 5A (based on the ASCII table).
#2. substr(…, 0, 4)
- Extracts the first 4 characters from the string 4d5a.
#3. strtoupper()
- Converts lowercase letters (4d5a) to uppercase: “4D5A”.
Result:
- The variable $magicbytes now contains the value “4D5A”.
6. Checking if the Magic Bytes Are Valid
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();
}
** The strpos() function checks if the string “4D5A” is present in $magicbytes.
** If “4D5A” is not found (false), the following actions occur:
**** echo prints the message “Error no valid PEFILE” to the screen.
**** error_log() logs this error in the server’s error log.
**** exit() immediately stops the script.
💡 Example:
- If the user uploads malicious.exe, the file passes because its hex value is 4D5A.
- If the user uploads not_a_pe.txt, the file is rejected because its hex value is not 4D5A.
7. Saving the File Permanently
rename($tmp_location, $location);
- The rename() function moves the temporary file to the $location path and renames it with its original name.
- For example, if the file is named virus.exe, it will now be found in the upload/malicious.exe folder.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
📚 Definitions
- Magic Bytes: Special bytes used to identify the type of a file. For example:
- PE files: “MZ” → 4D5A
- PNG images: “PNG” → 89504E47
- PDF documents: “%PDF” → 25504446
2. PE (Portable Executable): A file format used for executable files (EXE, DLL, SYS, etc.) on Windows operating systems.
3. Hexadecimal (Hex): A number system that uses digits 0–9 and letters A-F. For example:
- M → 4D
- Z → 5A
4. ASCII: A system that converts characters into numerical values. For example:
- M = 77 (decimal), 4D (hex)
- Z = 90 (decimal), 5A (hex)
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -
🗂️ Summary
This PHP code allows only PE files to be uploaded to the server. Here’s how it works:
- The file is first uploaded as a temporary file.
- The first 2 bytes are read to check if they match “MZ” (4D5A).
- If the file is valid, it’s saved permanently with its original name.
- If it’s invalid, an error message is displayed, and the process is terminated.
Now, it’s time to design your file to bypass this check :
From the revshells.com, I copied PentestMonkey’s PHP Reverse Shell as revshell.php and added the “MZ” bytes at the beginning of the code.

Then upload it :

And call back the file from the /upload directory :

Great! We have initial access. Whenever I land on a shell, the first thing I do is upgrading it :
python3 -c ‘import pty;pty.spawn(“/bin/bash”)’
CTRL+Z
stty -raw echo;fg
After I transferred & run linpeas, I found a strange suid bit :

Let’s try to get help :

Let’s check its version to find out if there is a potential exploit :

Guys, to be honest, I’m exhausted at this moment and I cannot understand what’s going on. Again, ChatGPT :
This output shows that when you run the ./fileS
command, it actually runs the GNU find
command. So, there might be something in the fileS
file that calls the find
command.
What’s Happening?
- Normally, the
./fileS --version
command should display the version of thefileS
file itself. - But here, you’re getting the output
find (GNU findutils) 4.8.0
, which indicates thatfileS
is somehow running thefind
command.
Why Is This Happening?
- The
fileS
file might be a symlink to thefind
command or could be a script that calls thefind
command.
So, this file is caling for the find command. It’s a suid bit and if we check the GTFOBins :

We can leverage this by :
./files -exec /bin/sh -p \; -quit

QED!
