This is an intermediate box on Offsec’s PG Practice and the community agrees that it is of intermediate difficulty.
Start with a Nmap scan:
sudo nmap -Pn -n $IP -sC -sV -p- --open
I start nearly every box this way because it quickly returns a wealth of information. Sudo as it defaults to the faster half-open SYN scan, then -Pn to ignore ping and assume it is up, -n to ignore DNS, the IP address, -sC for default scripts, -sV for version information, -p- to scan all ports, and MOST importantly the — open argument to apply scripts and version scans to found open ports only.
└─$ sudo nmap -Pn -n $IP -sC -sV -p- --open
[sudo] password for kali:
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-02-29 09:21 EST
Nmap scan report for 192.168.194.33
Host is up (0.090s latency).
Not shown: 63421 closed tcp ports (reset), 2112 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
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-title: MZEE-AV - Check your files
|_http-server-header: Apache/2.4.56 (Debian)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
When the TCP scan finishes, immediately run a UDP scan.
sudo nmap -Pn -n $IP -sU --top-ports=100 --reason
Even if it comes up empty, scanning the UDP ports as well is best practice. When it hits, it’s usually a big deal, maybe Simple Network Management Protocol (SNMP) is available. Note that I am only interested in the top 100 ports. UDP scans take a long time if we are not precise. Lastly we add — reason to understand why ports are returned as open, open|filtered, or closed.
What do we see with our initial TCP Nmap scan?
Only two ports open, a web server on port 80 and SSH on port 22. It’s always a bit of a relief to see this output since we know that access will be though the Web server. In that sense the port determination is uncomplicated.
As we do, let’s start Gobuster against the web server first.
sudo gobuster dir -w '/home/kali/Desktop/wordlists/dirbuster/directory-list-2.3-medium.txt' -u http://$IP:9000 -t 42 -b 400,401,403,404 --no-error
I run it with 42 threads (-t 42) because it’s a good balance of speed and caution. If you go too fast, directories and files can be missed. Also, I’m not bug hunting, so I’m not going to dive deeply into fuzzing different 400 codes. They are just visual noise to me, so I eliminate them (-b 400,401,403,404). Lastly, I loathe seeing the ‘Time Exceeded’ errors so we eliminate them with — no-errors.
While this runs we looks at the other port (mostly for notes).
Port 22 — SSH, OpenSSH 8.4p1 Debian 5+deb11u2 (protocol 2.0)
SSH exploits are extremely rare and difficult to exploit if they exist (i.e. Terrapin). My process is to grab the type of accepted key in case it comes up later.
Port 80 — Web Server, Apache 2.4.56 ((Debian))
Let’s browse there now.
We are greeted with immediate upload functionality, gotta love that! The door is partially open already. I am not familiar with this tool, let’s see of we can find the source code on Github. The only time I’ve ever encountered the work ‘Mzee’ is on Words with Friends. It’s a tricky high-scoring word there.
Google was also uncharacteristically silent on this, I gather this is custom code or it was buried with the dead bodies on page two of the Google search.
Before I start playing with uploads, I would like to know what other technology the Apache Web Server is using.
Gobuster found some useful directories which we can look at in a minute but first I want to scan again with some file extensions to get a better grasp on the language used. Is this PHP? I add -x php,txt,html,pdf to search for likely file types.
sudo gobuster dir -w '/home/kali/Desktop/wordlists/dirbuster/directory-list-2.3-medium.txt' -u http://$IP:80 -t 42 -b 400,401,403,404 --no-error -x php,txt,html,pdf
Best practice is to also fire up Burp Suite in case we need to do some proxy manipulation of file extension or mime type. I’ll leave interception ‘off’ for now and set FoxyProxy to Burp. This will create a history for us if we need it.
Gobuster has already answered my primary question, this application does indeed use PHP. Also it discovered an important endpoint ‘listing.php’. I’m glad we ran this, I would not have know it was there otherwise.
We are going to investigate all of these end points now.
Then the source:
They give us the entire script logic and the two PHP endpoints.
Let’s ask Google Gemini what this is doing exactly:
My Prompt —
Speak with me as a cyber security professional. The template I am using for my website contains an upload script. What is it doing? Is it vulnerable and if so, how? Please explain every step in detail. Here is the script:
<script>
async function uploadFile() {
let formData = new FormData();
formData.append("file", fileupload.files[0]);
await fetch('/upload.php',
{ method: "POST",
body: formData });
alert('The file has been uploaded successfully.');
window.location = "listing.php"; }
</script>
Gemini’s Answer —
Analyzing the website upload script:
Function:
This script defines an asynchronous function named
uploadFile
that handles file uploads to the server. Let's break down the steps:
let formData = new FormData();
:This line creates a new
FormData
object, which is specifically used to construct multipart/form-data requests suitable for file uploads.
formData.append("file", fileupload.files[0]);
:This line attaches the user-selected file to the
FormData
object.
"file"
: This is the key used to identify the uploaded file on the server-side script.
fileupload.files[0]
: This refers to the first file selected by the user in the "fileupload" element (likely an HTML file input element).
await fetch('/upload.php', { method: "POST", body: formData });
:This line initiates a file upload request to the server using the
fetch
API.
/upload.php
: This specifies the URL of the server-side script that handles the upload process.
method: "POST"
: This indicates that the request is a POST request, commonly used for submitting form data like file uploads.
body: formData
: This sets the body of the request to the previously createdformData
object, containing the file data.
await
: This keyword signifies that the function pauses execution at this point until the fetch operation completes (the file upload is finished).
alert('The file has been uploaded successfully.');
:This line displays an alert message to the user indicating successful file upload (assuming the server-side script processes the upload successfully).
window.location = "listing.php";
:This line redirects the user’s browser to the “listing.php” page after the upload is complete.
Vulnerabilities:
1. Insecure file type handling:
The script doesn’t perform any validation on the uploaded file type. This means a malicious user could potentially upload a script or executable file disguised as an image or document, posing a security risk.
2. Lack of input validation:
The script doesn’t validate the filename or any other user-provided data within the
FormData
object. This could allow attackers to inject malicious code or manipulate the server-side logic through techniques like directory traversal attacks.3. Missing access control:
The script doesn’t implement any access control mechanisms to restrict who can upload files. This could allow unauthorized users to upload files to your website.
4. Potential for server-side vulnerabilities:
Even if the client-side script is secure, vulnerabilities in the server-side script that handles the uploaded file (
/upload.php
) could still be exploited.
Thanks Gemini!
Note that this is useful for learning to understand the code we encounter but as of this writing, the use of AI is not permitted on the OSCP exam.
We will explore how to weaponize this knowledge in a minute but for now let’s complete our checklist by looking at every endpiont.
It’s worthwhile to run a Gobuster against this directory. I am going to also look for PHP and HTML files types since we have encountered those elsewhere in the application.
Moving on.
This is interesting. I wonder if all of these files can be directly accessed? If so, where are they? In the root directory or in /upload maybe? I did not search for file type EXE. We can make a note to try that in a second round of enumeration in the event that nothing else pans out. I check the source page here as a matter of completion but it is only HTML, not useful.
Well well well, what do we have here? A backup you say?
Okay good, let’s review our position. Our two most likely access potentialities are to exploit the upload logic with a PHP rev-shell and access it or find something valuable, like and SSH key, in the backup file which appears to be quite large. It makes the most sense to have all the information before we go exploiting, so I’m going to grab the ZIP file now.
It’s not even locked with a password and it turns out there were EXE files in the upload directory, so this is also a confirmation that uploaded file goes to the upload directory.
I know this seems obvious, but this type of step by step confirmation will serve you in the long run. In my opinion, this is what solid process looks like. The directory containing uploaded files will not always be so obviously named.
We can also guess by this that there may be a cron job running on the box that regularly creates a backup of /var/www/html/. We should look for that when we get access, if it’s being run as root, we could have our escalation path.
This is a good time to use my favorite grep command given to me by Krishna Tiwadi. It is good for download scenarios like this one where we have to sift through a lot of data.
grep -rinE '(password|username|user|pass|key|token|secret|admin|login|credentials)' /path/to/downloaded/files
Since we have this backup we can look at the exact logic of upload.php to see where it is vulnerable.
┌──(kali㉿kali)-[~/…/TEMP-publish/var/www/html]
└─$ cat 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);
?>
We can again ask Gemini about this file. Here what she said:
Analysis of upload.php
This script implements a basic check to block a specific type of file during the upload process. Here’s a breakdown:
Functionality:
Retrieving file information:
The script retrieves the uploaded file’s name (
$filename
) from the$_FILES
superglobal variable.Temporary storage:
It defines temporary (
$tmp_location
) and final ($location
) storage locations for the uploaded file.Moving the uploaded file:
The
move_uploaded_file
function is used to move the uploaded file from its temporary location to the designated temporary storage ($tmp_location
).Checking magic bytes:
The script opens the temporarily stored file (
$tmp_location
) and reads the first two bytes (fread(F, 2)
) into a variable named$magic
.It then converts the raw bytes to a hexadecimal string using
bin2hex($magic)
, extracts the first four characters (substr(..., 0, 4)
), and converts them to uppercase (strtoupper(...)
). This process essentially retrieves the "magic bytes" of the file, which are characteristic bytes that can identify the file type.The script stores the extracted magic bytes in the
$magicbytes
variable.It logs the magic bytes using
error_log
for potential debugging or monitoring purposes.Blocking specific file type:
The script checks if the
$magicbytes
string does not contain the specific sequence'4D5A'
. This sequence is the magic byte signature for Portable Executable (PE) files, commonly used for executable programs in Windows systems.If the magic bytes don’t match the expected signature, the script displays an error message (“Error no valid PEFILE”), logs additional information for debugging, and terminates the script using
exit()
.Renaming and final storage:
If the magic bytes match the expected signature (indicating a non-PE file), the script renames the temporarily stored file (
$tmp_location
) to its final location ($location
).
Basically the upload script is using a Magic Bytes Whitelist that only allows the Portable Executable signature of 4D5A.
So, as long as our file contains the magic bytes 4D5A
, then the script should allow us to upload it.
Great! We know how to do that.
First in a file editor open up your favorite PHP rev-shell, either Pentest Monkey or the Ivan Sincek version we can find on revshells.com.
At the beginning of the file, add two uppercase A’s then save and close.
In hexadecimal this becomes 41. We can see this when we open the file again in a hex editor.
We wan to change this to match our whitelist.
If you look to the right in the hex editor, you can see that the ASCII value becomes ‘MZ’.
Press CTRL+X to close and save the file.
Now we should be able to upload or file.
This looks good, now let’s set up our listener. If you also use Peneope, give yourself a pat on the back.
Now we will try to navigate to the file and cross our fingers.
Check the listener.
It connected but we see that our shell type is BASIC, meaning Penelope did not automatically upgrade our shell for us and we will have to do it manually. This also means our arrow key functionality is not preserved and we cannot use the F12 menu key. This rarely happens.
Here we go:
python3 -c 'import pty;pty.spawn("/bin/bash")'
export TERM=xterm
(CTRL+Z to background)
stty raw -echo; fg
(Press enter)
We landed in the upload directory and since there are no surprises there, I’m going to go to /home.
Check for permissions. (There was a long pause here. The server failed to resolve it’s own hostname)
After this my attempt to bring enumeration scripts over to victim machine failed and I encountered unexplained persistent shell errors. I canceled out of it.
Forget it, we will do it again a little bit differently with a new listener. Sorry Penelope!
All you have to do is refresh the (now errored out) rev-shell browser page.
We will have to manually upgrade again.
Unfortunately this time we do not get tab auto-completion. We have not matched the window size but I am not using a text editor at the moment so I will let it slide. This is not a perfectly upgraded shell. The last step for me is always hit or miss and I have never quite figured out why. Do you know? Should I have started from a Bash and not a Zsh shell?
Moving on:
Run them and I will post the sections I find interesting.
Now for the other enumeration script. Be sure to run it with the ‘-l1’ argument to go one level deeper.
Okay, let’s see what we can do with what we have learned.
The permissions for this are super weird. Is this a misconfiguration? Who puts write but not read access and why?
Let’s look at the other item of interest, like custom binary /opt.fileS
Playing around with it gives us a clue as to its functionality. It appears to list all the directory and subdirectory contents including hidden files.
Can we use it to read directories that are other wise unreadable?
Yes it can. Now for our unreadable directory.
That did not help. It is running as root, can I get it to run another command? Does it stay elevated?
What does this do exactly? I need help.
Hey there we go!
That did not work as I wished. I’m going to look at the help again.
This seems to be implying it is a binary similar to find.
GTFOBins already has a solution to escalate via a find SUID binary.
Might as well try it. Look there is that ‘exec’ argument! Should I have been using the absolute path before? (Always, it is always best practice to use absolute paths.)
So the command fitted to our purpose is:
/opt/fileS . -exec /bin/bash -p \; -quit
We did it!
I am thankful for everyone that read my exploration of this challenge. I do this because I enjoy it, hopefully you do as well.
Thanks always to Offensive Security.