What Are File Upload Vulnerabilities?#
These vulnerabilities are often present when web applications contain file upload functionality that do not sufficiently validate things like the file’s name, type, contents, or size.
Failure to validate these properties could mean something as simple as uploading a profile picture could lead to remote code execution. In some cases, uploading the file itself can cause damage by replacing important system files.
What is the Impact of File Upload Vulnerabilities?#
It usually depends on two things:
- Which aspect of the file is not validated properly?
- Size, file name, location, contents, etc..
- What restrictions are imposed on the file once uploaded?
In the worst case scenario, the file type is not validated and the server is configured to execute certain file types like .php and .jsp as code. This would allow an attacker to upload a web shell, which would give them the ability to control the web server.
If the file name isn’t validated, attackers might be able to overwrite critical system files by uploading a file with the same name. This in conjunction with directory traversal could lead to some big problems.
If you fail to validate the size of the uploaded file, then attackers might be able to upload a file so large that it uses all the available disk space on the server, causing a denial of service.
How Do These Vulnerabilities Arise?#
It is uncommon to find websites with no protections in place because of the potential impact of these kinds of vulnerabilities. What is more common is that developers implement protections that they believe are robust, but ultimately contains some flaws that allow the protection to be bypassed.
Developers might try to blacklist file extensions, but fail to account for certain edge cases. It is also possible that the way their protections parse the file name and details could contain flaws.
In other cases, the application might try to verify the file type by checking certain properties that attackers can manipulate.
Keep in mind that even the most robust protections can be inconsistently applied on an application, leading to exploitation.
How Do Web Servers Handle Requests for Static Files?#
Historically, websites were almost entirely made up of static files that led to one another which would create a one-to-one map of the file system and the application.
In the present, websites are a lot more dynamic and the request path often has little to do with the underlying file system. There are still static files used like stylesheets, images, and so on.
Handling these static files usually involves the server parsing the path in the request to identify the file extension. This parsing operation determines the type of file that is being requested and compares it to a list of pre-conceived mappings of MIME types (the media type). Then:
- If the file is an non-executable like an image or static HTML page, the server might just send the contents via an HTTP response.
- If the file is an executable like a
phpfile and the server is configured to execute files of this type, it will assign variables based on the headers and parameters in the HTTP request before running the script. The output might then be sent to the client in an HTTP response. - If the file is executable but the server is not configured to execute files of that type, it will usually respond with an error.
- This is not always the case though, sometimes the contents are still served to the client as plain text which can occasionally be used to leak information from the site.
Exploiting Unrestricted File Uploads for a Web Shell#
For reference, web shells are just the means we use to execute commands on a web server. Once we upload an executable file, it can execute commands for us when we navigate to that file in a URL.
For developers, the worst case scenario is if we a re able to upload an executable file and get the server to run that code. At that point, the attacker would have control of the web server and could do nearly anything with the application data.
For example, we could use a php one liner like this:
<?php echo system($_GET['cmd']); ?>
This would let us pass system commands by sending requests to the following endpoint:
GET /uploads/evil.php?cmd=id HTTP/1.1
Exploiting Flawed File Upload Validation#
As we mentioned before, most sites will have protections put in place, so we will need to come up with ways to bypass the common protections you might run into.
Flawed file type validation#
When submitting forms, the browser typically sends data in POST requests with the file type specified like this: application/x-www-form-url-encoded.
This isn’t really suitable for sending larger amounts of data like images or PDFs, so the content type will usually be set to multipart/form-data.
For example, you might upload an image and see the following POST request being sent:
POST /images HTTP/1.1
Host: vulnerable.com
Content-Length: 12345
Content-Type: multipart/form-data; boundary=---------------------------012345678901234567890123456
---------------------------012345678901234567890123456
Content-Disposition: form-data; name="image"; filename="cat.jpg"
Content-Type: image/jpeg
[...binary content of cat.jpg...]
---------------------------012345678901234567890123456
Content-Disposition: form-data; name="description"
This is a cool cat picture.
---------------------------012345678901234567890123456
Content-Disposition: form-data; name="username"
gabe
---------------------------012345678901234567890123456--
The message body is split into different parts where each section contains a Content-Disposition header that gives details about that section’s data. These sections might also contain a Content-Type header, which tells the server the media type.
Websites attempt to validate using the aforementioned headers, but if these headers are implicitly trusted, you can run into problems if an attacker changes them.
Preventing file execution in user-accessible directories#
If you can’t prevent a file from being uploaded, you will want to set up defenses to stop files from being executed. Most web servers will only run scripts if the server was explicitly configured to run that specific media type.
When the file can’t be executed the server usually just returns the file contents, which can be used to leak source code in some instances.
It is important to keep in mind that this configuration often differs between directories and sometimes you can manipulate the filename to get your malicious file placed in a different directory where it can be executed.
Insufficient blacklisting of dangerous file types#
One obvious way to try and stop users is to blacklist file extensions like .php but keep in mind that it is pretty easy to forget edge cases when blacklisting. For example, blacklists can sometimes be bypassed by using lesser known, alternative file extensions that may still be executable, such as .php5, .shtml, and so on.
HTB Perspective#
This machine has a web application that lets us add new products by utilizing the functionality at the perspective.htb/Produces/NewProduct endpoint. When we add a new product we are able to upload image files, let’s see if we can work our way to a web shell or file read!
We can capture a request to upload a product and we see the following data in the body of the request:
---SNIP---
-----------------------------2561626301355944151963280683
Content-Disposition: form-data; name="ctl00$MainContent$FileUploadControl"; filename="cat-picture.jpg"
Content-Type: image/jpeg
ÿØÿá
---SNIP---
We can try modifying the Content-Type parameter to see if it will let us upload a php file:
---SNIP---
-----------------------------2561626301355944151963280683
Content-Disposition: form-data; name="ctl00$MainContent$FileUploadControl"; filename="cat-picture.jpg"
Content-Type: application/php
ÿØÿá
---SNIP---
This attempt doesn’t seem to work, neither does changing the filename parameter.
If we remove all the image data in the request body though, it seems to upload just fine:

And we see it was successful by following the redirect and viewing the new products:

If we copy the link to the image, it fails to display but using curl will reveal the test data that we included:
╰─ curl http://perspective.htb/Images/cat-picture_4387205545.jpg
test data
This tells us that the site is not filtering based off of the actual content of the file itself or the magic bytes included in the file type.
We also see that the file name has some random bits added to the end of it which might be important. We can use the Burp Intruder to see which file extensions are acceptable.
The file names can’t be repeated but sending an invalid file type will give us a separate error, and we can use this to determine which extensions are allowed:

If we try php3 or php4, it uploads but the site doesn’t seem configured to execute those kinds of files because when we navigate to the file it gives us a 404 error.
When we use the shtml extension though, it works just fine and we see our text data rendered on the page. We can try using some HTML and use syntax to read the file.
In this case, we want to read the web.config file which works similarly to the .htaccess file but is what IIS applications use.

I had to back out one directory, but when we view this file in our browser it lets us read the contents of the file, which in this case shows us information that we can use later to forge an admin cookie.
Obfuscating file extensions#
Even the longest blacklists will probably have some kind of place where it missed the mark. Let’s go over some examples to try out when you are uploading files:
- Provide multiple extensions:
exploit.php.jpg - Add trailing characters:
exploit.php. - Try encoding the file names:
exploit%2Ephp - Try adding semicolons or null bytes to cause discrepancies:
exploit.php;.jpgorexploit.php%00.jpg
Some defenses will try to strip the file extension from the file name, which can be bypassed if it implemented poorly. For example if any string .php is removed from the input filename then we can submit this:
exploit.p.phphp
To bypass that stripping operation and upload it anyways. This should give you a flashback to path traversal workarounds.
HTB Magic#
We can log in with an SQLi payload in the username field like this:
' or 1=1-- -
Then, we can upload an image and see what restrictions are present.
After testing a bit, you’ll see that the application does check the MIME type, because when you replace the image data with just text, it doesn’t upload the file. Next, changing the file extension in the filename parameter also results in an error.
If we play around some more, we can realize that the file name can be changed to something ending in .php.jpg and no errors will be thrown.
Cutting off a bunch of the image data, adding in a php command execution payload, and (for some reason I am not aware of) add more image fluff to the end, we will be able to execute commands on the web server:

From here we could get a shell back or read files if we wanted to.
Flawed validation of the file’s contents#
Instead of implicitly trusting the Content-Type specified in a request, more secure servers try to verify that the contents of the file actually match what is expected.
The server might try to verify attributes like the dimension of the image or by looking for certain bytes at the header or footer of the file. Although this is more effective and robust, there are still tools like exiftool that you can use to get a file to appear to be an image.
Exploiting file upload race conditions#
It is more common for modern frameworks to place a file in a sandboxed directory and give it a random filename, then performing validation. This avoids the risk of file overwriting and path traversal.
Some developers will implement this themselves, which is pretty complex to do and can introduce race conditions that let attackers bypass the validation.
For example, some websites upload the file directly to the main filesystem and then remove it again if it doesn’t pass validation. This kind of behavior is typical in websites that rely on anti-virus software and the like to check for malware. This may only take a few milliseconds, but for the short time that the file exists on the server, the attacker can potentially still execute it.
These will be really subtle, but still worth going over.
Prevention#
If you provide the right protections, you can remove most of the danger associated with file uploads:
- Check the file extension against a whitelist of permitted extensions rather than a blacklist of prohibited ones.
- Make sure the filename doesn’t contain any substrings that may be interpreted as a directory or a traversal sequence (
../). - Rename uploaded files to avoid collisions that may cause existing files to be overwritten.
- Do not upload files to the server’s permanent filesystem until they have been fully validated.
- As much as possible, use an established framework for preprocessing file uploads rather than attempting to write your own validation mechanisms.