File upload vulnerabilities are when a web server allows users to upload files to its filesystem without sufficiently validating things like their name, type, contents, or size. Failing to properly enforce restrictions on these could mean that even a basic image upload function can be used to upload arbitrary and potentially dangerous files instead. This could even include server-side script files that enable remote code execution.
In some cases, the act of uploading the file is in itself enough to cause damage. Other attacks may involve a follow-up HTTP request for the file, typically to trigger its execution by the server.
The impact of file upload vulnerabilities generally depends on two key factors:
- Which aspect of the file the website fails to validate properly, whether that be its size, type, contents, and so on.
- What restrictions are imposed on the file once it has been successfully uploaded.
In the worst case scenario, the file's type isn't validated properly, and the server configuration allows certain types of file (such as
.jsp) to be executed as code. In this case, an attacker could potentially upload a server-side code file that functions as a webshell, effectively granting them full control over the server.
Historically, websites consisted almost entirely of static files that would be served to users when requested. As a result, the path of each request could be mapped 1:1 with the hierarchy of directories and files on the server's filesystem. Nowadays, websites are increasingly dynamic and the path of a request often has no direct relationship to the filesystem at all. Nevertheless, web servers still deal with requests for some static files, including stylesheets, images, and so on.
The process for handling these static files is still largely the same. At some point, the server parses the path in the request to identify the file extension. It then uses this to determine the type of the file being requested, typically by comparing it to a list of preconfigured mappings between extensions and MIME types. What happens next depends on the file type and the server's configuration.
- If this file type is non-executable, such as an image or a static HTML page, the server may just send the file's contents to the client in an HTTP response.
- If the file type is executable, such as a PHP file, 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 resulting output may then be sent to the client in an HTTP response.
- If the file type is executable, but the server is not configured to execute files of this type, it will generally respond with an error. However, in some cases, the contents of the file may still be served to the client as plain text. Such misconfigurations can occasionally be exploited to leak source code and other sensitive information.
Content-Typeresponse header may provide clues as to what kind of file the server thinks it has served. If this header hasn't been explicitly set by the application code, it normally contains the result of the file extension/MIME type mapping.
From a security perspective, the worst possible scenario is when a website allows you to upload server-side scripts, such as PHP, Java, or Python files, and is also configured to execute them as code. This makes it trivial to create your own web shell on the server.php
A webshell is a malicious script that enables an attacker to execute arbitrary commands on a remote web server simply by sending HTTP requests to the right endpoint.
If you're able to successfully upload a web shell, you effectively have full control over the server. This means you can read and write arbitrary files, exfiltrate sensitive data, even use the server to pivot attacks against both internal infrastructure and other servers outside the network. For example, the following PHP one-liner could be used to read arbitrary files from the server's filesystem:
<?php echo file_get_contents('/path/to/target/file'); ?>
Once uploaded, sending a request for this malicious file will return the target file's contents in the response. A more versatile web shell may look something like this:
<?php echo system($_GET['command']); ?>
This script enables you to pass an arbitrary system command via a query parameter as follows:
GET /example/exploit.php?command=id HTTP/1.1
Windows is case-insensitive
On Windows machines,
test.txt and test.tXt
Therefore, we can rename
webshell.pHpto bypass file extension restriction on IIS. You can also achieve similar results using the following techniques:
- Provide multiple extensions
- Depending on the algorithm used to parse the filename, the following file may be interpreted as either a PHP file or JPG image:
- Add trailing characters
- Some components will strip or ignore trailing whitespaces, dots, and suchlike:
- Try using the URL encoding (or double URL encoding) for dots, forward slashes, and backward slashes
- If the value isn't decoded when validating the file extension, but is later decoded server-side, this can also allow you to upload malicious files that would otherwise be blocked:
- Add semicolons or URL-encoded null byte characters before the file extension
- If validation is written in a high-level language like PHP or Java, but the server processes the file using lower-level functions in C/C++, for example, this can cause discrepancies in what is treated as the end of the filename:
- Try using multibyte unicode characters, which may be converted to null bytes and dots after unicode conversion or normalization
- Sequences like
xC0 xAEmay be translated to
x2Eif the filename parsed as a UTF-8 string, but then converted to ASCII characters before being used in a path.
Web servers such as IIS, Nginx, and Apache have their own vulnerabilities:
If the upload module uses blacklist that blocks certain types of file extensions, for example,
.php, we can define a new file extension such as
.testand let the web server execute this new file extension as PHP file. We do this by uploading
.htaccessfor Apache or
On Windows machines,
test.txt::$DATArefer to the same data stream. That is, writing to
test.txt::$DATAis equivalent to writing to
test.txt. We can utilize this feature for upload bypass.
Consider the following upload module implementation:
$is_upload = false;
$msg = null;
$ext_arr = array('jpg', 'png', 'gif');
$file_ext = substr($_FILES['upload_file']['name'], strrpos($_FILES['upload_file']['name'], ".")+1);
$temp_file = $_FILE['upload_file']['tmp_name'];
$img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
$is_upload = true;
$msg = "Upload failed.";
$msg = "Only .jpg|.png|.gif files are allowed!";
If filename is passed in as URL, then we can named the webshell
webshell.php%00.png. The parser will take this file as a legit
.pngimage and store it. If the filename is passed in as HTTP header, we can named the webshell
webshell.php .pngand change the whitespace to a null byte in Burp's hex editor.
If the upload module determines file extension based on file signature, we can simply append a webshell to the end of a regular image file:
cat webshell.php >> image.png
Modern frameworks are more battle-hardened against file upload attacks. They generally don't upload files directly to their intended destination on the filesystem. Instead, they take precautions like uploading to a temporary, sandboxed directory first and randomizing the name to avoid overwriting existing files. They then perform validation on this temporary file and only transfer it to its destination once it is deemed safe to do so.
That said, developers sometimes implement their own processing of file uploads independently of any framework. Not only is this fairly complex to do well, it can also introduce dangerous race conditions that enable an attacker to completely bypass even the most robust 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 vulnerabilities are often extremely subtle, making them difficult to detect during blackbox testing unless you can find a way to leak the relevant source code.
Similar race conditions can occur in functions that allow you to upload a file by providing a URL. In this case, the server has to fetch the file over the internet and create a local copy before it can perform any validation.
As the file is loaded using HTTP, developers are unable to use their framework's built-in mechanisms for securely validating files. Instead, they may manually create their own processes for temporarily storing and validating the file, which may not be quite as secure.
For example, if the file is loaded into a temporary directory with a randomized name, in theory, it should be impossible for an attacker to exploit any race conditions. If they don't know the name of the directory, they will be unable to request the file in order to trigger its execution. On the other hand, if the randomized directory name is generated using pseudo-random functions like PHP's
uniqid(), it can potentially be brute-forced.
To make attacks like this easier, you can try to extend the amount of time taken to process the file, thereby lengthening the window for brute-forcing the directory name. One way of doing this is by uploading a larger file. If it is processed in chunks, you can potentially take advantage of this by creating a malicious file with the payload at the start, followed by a large number of arbitrary padding bytes.
It's worth noting that some web servers may be configured to support
PUTrequests. If appropriate defenses aren't in place, this can provide an alternative means of uploading malicious files, even when an upload function isn't available via the web interface.
PUT /images/exploit.php HTTP/1.1
<?php echo file_get_contents('/path/to/file'); ?>
You can try sending
OPTIONSrequests to different endpoints to test for any that advertise support for the
find -mmin n -name *.php /var/www/html/
- Find PHP files in
/var/www/html/that are modified in the last
find . -name "*.php" -print0 | xargs -0 grep -rn 'shell_exec'
- Check if any PHP file contains
grep -i keyword "eval"
- If keyword
evalis found, print that line.
File upload vulnerability arises when the following three requirements are satisfied:
- 1.File extension is not properly filtered and the attacker is able to upload a webshell.
- 2.The attacker has permission to visit the upload directory.
- 3.Filename is fixed or guessable so that the attacker is able to locate it.
The defense idea is:
- 1.Whitelist file extensions and handle uploaded files carefully.
- 2.Don't give upload directory permission to users.
- 3.Randomize filename.
Don't reinvent the wheel in production environment. Use an established framework.