THM – Upload Vulnerabilities – Part 13

This is a continued series where I document my path through different tryhackme courses. I recommend everyone that wants to learn cyber security to subscribe to tryhackme.com and take the courses there.

Introduction

Badly handled file uploads can lead to Remote Code Execution (RCE) if an attacker manages to upload and execute a shell.

Will learn about vulnerabilities resulting from improper (or inadequate) handling of file uploads:

  • Overwriting existing files on a server
  • Uploading and Executing Shells on a server
  • Bypassing Client-Side filtering
  • Bypassing various kinds of Server-Side filtering
  • Fooling content type validation checks

Methodology – Enumeration

  • Look at the source code of the page. See if there is a client-side filter being applied.
  • Scanning with a directory bruteforcer such as Gobuster to reveal where files are being uploaded.
  • Intercepting upload requests with Burpsuite.
  • Upload files to provoke errors.

Overwriting Existing Files

When uploading a file to a server, a range of checks should be carried out to ensure that the file will not overwrite anything which already exist on the server.

Common practice: Assign the file with a new name – Often random or with the date and time of upload added to the start or end of the original filename.

Alternative: check to see if the filename already exists on the server; if a file with the same name already exist then the server will return an error message.

File permissions: Web pages should not be writeable to the web user. This prevent them from being overwritten with a malicious version uploaded by an attacker.

Remote Code Execution

Remote Code Execution would allow attacker to execute code arbitrarily on the web page. RCE through a web application tends to be a result of uploading a program written in the same language as the back-end of the website, or another language that the server understands and will execute.

Two basic ways to achieve RCE on a webserver: webshells and reverse shells.

Web shells Example:

Do a gobuster scan:

Try uploading a legitimate image file.

The uploaded file should be on the website http://demo.uploadvulns.thm/uploads.

We know the webserver is running a PHP back-end, so we will create and upload the shell.

Example of a PHP syntax that would take a parameter and execute it as a system command.

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

This code takes a GET parameter and executes it as a system command. It the echoes the output on the screen.

Viewing the output by looking at the source code of the page is easier and improves formatting.

Reverse shells Example:

Process for uploading a reverse shell is almost identical to uploading a webshell. We will upload a PHP-reverse-shell and change the LHOST and LPORT.

We will then start an netcat listener on our machine. nc -lvnp 4444.

Navigating to the uploaded shell will activate it. The website should hang and not load properly. Looking at our terminal we will have a connection.

We have obtained RCE on the webserver. We should now stabilize the shell and escalate our privileges.

Filtering

We’ll be looking at some of the defense mechanism used to prevent malicious file uploads and how to circumvent them.

Difference between client-side filtering and server-side filtering.

Client-side filtering

The filter is running in the user’s browser as opposed to on the web server itself. JavaScript is pretty much always the client-side scripting language, although alternatives exists.

This means that the filtering occurs before the file is uploaded to the server. Since the filtering is happening on our computer its easy to bypass. Client-side filtering by itself is very insecure method of verifying that and uploaded file is not malicious.

Server-side filtering

Server-side script will run on the server. PHP was predominant server-side language (with MS’s ASP for IIS coming in second place). This has changed in recent years as other options (C#, Node.js, Python, Ruby on Rails etc) has become widely used. Server-side filtering is more difficult to bypass since the code is not in front of you. In most cases it would be impossible to bypass the filter completely, thus we have to form our payload which conforms to the filter in place, but still allows us to execute our code.

Example of different kinds of filtering.

Extension Validation

File extensions are used to identify the contents of a file. They are very easy to change, so they don’t mean much. MS Windows still uses them to identify file types, Unix based systems tend to rely on other methods.

Extension filters works in two ways: Blacklist extensions (List of extensions which are not allowed) or whitelist extensions (List of extensions which are allowed and reject everything else).

File Type Filtering

Similar to Extension validation, but more intensive. File type filtering looks to verify that the contents of a file are acceptable to upload. Two types of file type validation:

  • MIME validation: MIME (Multipurpose Internet Mail Extension) types are used as an identifier for files – originally when transferred as attachments over email, but now also when files are being transferred over HTTP(S). MIME type for a file upload is attached in the header of the request.
  • MIME types follow the format ‘<type>/<subtype>’. Request above; image “spaniel.jpg” was uploaded to the server. Since this is a legitimate JPEG image, the MIME type for this upload was “image/jpeg”. MIME type for file can be checked client-side and/or server-side. MIME is extremely easy to bypass.
  • Magic Number validation: More accurate way of determining the contents of a file; but possible to fake. Magic number of a file is a string of bytes at the very beginning of the file content which identify the content. For example: PNG would have ‘89 50 4E 47 0D 0A 1A 0A’ at the very top of the file.

Unix use magic numbers for identifying files, Windows don’t. More effective than checking the extension of a file.

File Length Filtering

File lengths filters are used to prevent huge files from being uploaded to the server via upload forms.

File Name Filtering

Files uploaded to a server should be unique. Usually by adding random aspects to the file name. Alternative strategy is to check if a file with the same name already exist on the server. File names should be sanitized on upload to ensure that they don’t contain any ‘bad characters’, which could potentially cause problems on the file system when uploaded (e.x null bytes or forward slashes on the linux, or characters such as ‘;’ and potentially Unicode characters).

File Content Filtering

More complicated filtering systems may scan the full contents of an upload file to ensure that its not spoofing its extensions, MIME type and magic number. This is more complex.


None of the mentioned filters are perfect by themselves – they will usually be used in conjunction with each other, providing a multi-layered filter, thus increasing the security of the upload significantly.

Bypassing Client-side Filtering

Four ways to bypass average client-side file upload filter:

  1. Turn off JavaScript in your browser – this will work if the site doesn’t require JavaScript to run in order to provide basic functionality.
  2. Intercept and modify the incoming page. Using burpsuite to intercept incoming web page and strip out the JavaScript filter before it has a chance to run.
  3. Intercept and modify the file upload. This method allows the web page to load as normal but intercepts the file upload after its already passed.
  4. Send the file directly to the upload point. Send the file directly using a tool like curl. osting the data directly to the page which contains the code for handling the file upload is another effective method for completely bypassing a client side filter. Syntax: curl -X POST -F "submit:<value>" -F "<file-parameter>:@<path-to-file>" <site>. To use this method you would first aim to intercept a successful upload (using Burpsuite or the browser console) to see the parameters being used in the upload, which can then be slotted into the above command.

Method two and three demonstration.

From the source code we can see a basic JavaScript function checking for the MIME type of uploaded files:

Filter is using a whitelist to exclude any MIME type that isn’t ‘image/jpeg’.

Intercept response from the server with Burpsuite.

Delete, comment out or break the JavaScript function before it has a chance to load.

Click forward until the site finishes loading and we are now free to upload any kind of file we want.

Burpsuite will not by default intercept any external JavaScript files that the web page is loading. To intercept JS go to options in Burpsuite, the under “intercept Client Requests”, edit the condition of the first line and remove ^js$|.


We will try uploading a file with a legitimate extension and MIME type, then intercept and correcting the upload with Burpsuite.

Take the reverse shell and rename to ‘shell.jpg’. As the MIME type (file extension) automatically checks out, the client-side filter lets our payload through.

Catch the request with burpsuite.

Observe that the MIME type of our PHP shell is currently image/jpeg. We’ll change this to text/x-php, and the file extension from .jpg to .php, then forward the request to the server:

Navigate to http://demo.uploadvulns.thm/uploads/shell.php having set up a netcat listener, we receive a connection from the shell!

Bypassing Server-Side Filtering: File Extensions

Unlike client side filtering, you cannot see server side filtering. We have to perform a lot of testing to build up an idea of what is or is not allowed through the filter, then put together a payload which conforms to the restrictions.

We’ll look at a website that’s using a blacklist for file extensions as a server side filter. There are different ways this could be coded, and the bypass we use depends on that. IRL we wouldn’t be able to see code for this, but here is an example.

<?php
    //Get the extension
    $extension = pathinfo($_FILES["fileToUpload"]["name"])["extension"];
    //Check the extension against the blacklist -- .php and .phtml
    switch($extension){
        case "php":
        case "phtml":
        case NULL:
            $uploadFail = True;
            break;
        default:
            $uploadFail = False;
    }
?>

The code is looking for the last period ( . ) in the filename and uses that to confirm the extension. We will try and bypass this filter.

Other ways the code could be working include:

  • Searching for the first period in the file name, or splitting the file name at each period and checking to see if any blacklisted extensions show up.

The code is filtering out .php and .phtml extensions. To upload a PHP script we have to find another extension. Wikipage for php gives us many options we can try. Many of them bypass the filter, but the server is configured not to recognise them as PHP files. 

.phar extension bypasses the filter and works, giving us a shell.

Another example.

Upload form.

We’ll try with a legitimate upload. We will upload an image spaniel.jpg.

This means JPEG is accepted. We will try a shell, shell.php. Which is rejected.

From here, enumarting is important. In previous example we saw that the code was using the pathinfo() PHP function to get the last few characters after the ( . ).

We’ll try uploading a filed called shell.jpg.php. We know that JPEG are accepted.

Pseudocode for this kind of filter may look something like this:

ACCEPT FILE FROM THE USER -- SAVE FILENAME IN VARIABLE userInput
IF STRING ".jpg" IS IN VARIABLE userInput:
    SAVE THE FILE
ELSE:
    RETURN ERROR MESSAGE

The file is uploaded. We navigate to the /uploads directory and confirms that the payload was successfully uploaded.

This is by no means an exhaustive list of upload vulnerabilities related to file extensions. As with everything in hacking, we are looking to exploit flaws in code that others have written; this code may very well be uniquely written for the task at hand. This is the really important point to take away from this task: there are a million different ways to implement the same feature when it comes to programming — your exploitation must be tailored to the filter at hand. The key to bypassing any kind of server side filter is to enumerate and see what is allowed, as well as what is blocked; then try to craft a payload which can pass the criteria the filter is looking for.

Bypassing Server-Side Filtering: Magic Numbers

Magic numbers are used as a more accurate identifier of files. The magic number of a file is a string of hex digits, and is always the first thing in a file.

Its possbile to use magic numbers to validate file uploads, simply by reading those first few bytes and comparing them against either a whitelist or a blacklist.

Can be very effective against PHP based webservers, but fail against other types of webserver.

An example:

Uploading a shell.php will give us an error, however uploading a JPEG the website gives us success.

Since we know that JPEG are accepted, we’ll try adding the JPEG magic number to the top of our shell.php file. On wikipedia there is a list of file sigunatures. (https://en.wikipedia.org/wiki/List_of_file_signatures)

We can see that the magic number for JPEG files are several. It dosent matter which we use, so we’ll use (FF D8 FF DB).

We can use Linux file command to check the file type of our shell.

The command tells us that the filetype is PHP.

We can see that the magic number we’ve chosen is four bytes long, so we’ll open up the reverse shell script and add four random characters on the first line. The characters dosent matter, so we’ll put 4 A.

Open hexeditor. We can see the number 41 four times. Thats our A.

Change this to the magic number we found earlier for JPEG files.

Save the file and use the command file again and see that we have successfully spoofed the filetype of our shell.

We can now upload the modified shell to the server.

And we have a shell.

Example methodology

We’ll look at this as a step-by-step process. Let’s say that we’ve been given a website to perform a security audit on.

  • The first thing we would do is take a look at the website as a whole. Using browser extensions such as the aforementioned Wappalyzer (or by hand) we would look for indicators of what languages and frameworks the web application might have been built with. Be aware that Wappalyzer is not always 100% accurate. A good start to enumerating this manually would be by making a request to the website and intercepting the response with Burpsuite. Headers such as server or x-powered-by can be used to gain information about the server. We would also be looking for vectors of attack, like, for example, an upload page.

  • Having found an upload page, we would then aim to inspect it further. Looking at the source code for client-side scripts to determine if there are any client-side filters to bypass would be a good thing to start with, as this is completely in our control.
  • We would then attempt a completely innocent file upload. From here we would look to see how our file is accessed. In other words, can we access it directly in an uploads folder? Is it embedded in a page somewhere? What’s the naming scheme of the website? This is where tools such as Gobuster might come in if the location is not immediately obvious. This step is extremely important as it not only improves our knowledge of the virtual landscape we’re attacking, it also gives us a baseline “accepted” file which we can base further testing on.
    • An important Gobuster switch here is the -x switch, which can be used to look for files with specific extensions. For example, if you added -x php,txt,html to your Gobuster command, the tool would append .php, .txt, and .html to each word in the selected wordlist, one at a time. This can be very useful if you’ve managed to upload a payload and the server is changing the name of uploaded files.
  • Having ascertained how and where our uploaded files can be accessed, we would then attempt a malicious file upload, bypassing any client-side filters we found in step two. We would expect our upload to be stopped by a server side filter, but the error message that it gives us can be extremely useful in determining our next steps.

Assuming that our malicious file upload has been stopped by the server, here are some ways to ascertain what kind of server-side filter may be in place:

  • If you can successfully upload a file with a totally invalid file extension (e.g. testingimage.invalidfileextension) then the chances are that the server is using an extension blacklist to filter out executable files. If this upload fails then any extension filter will be operating on a whitelist.
  • Try re-uploading your originally accepted innocent file, but this time change the magic number of the file to be something that you would expect to be filtered. If the upload fails then you know that the server is using a magic number based filter.
  • As with the previous point, try to upload your innocent file, but intercept the request with Burpsuite and change the MIME type of the upload to something that you would expect to be filtered. If the upload fails then you know that the server is filtering based on MIME types.
  • Enumerating file length filters is a case of uploading a small file, then uploading progressively bigger files until you hit the filter. At that point you’ll know what the acceptable limit is. If you’re very lucky then the error message of original upload may outright tell you what the size limit is. Be aware that a small file length limit may prevent you from uploading the reverse shell we’ve been using so far.

Similar Posts