Uploading a file is a common web application task. In many cases, however, the sole act of successfully uploading anything to the server is considered a job done. This is what threat actors lurk for. The job is done when and only when both of these conditions are true: it uploads, and it uploads securely. In this post, we will explore what a user with bad intentions can do with an unsecured file upload, and we will see how we can mitigate it.
The Request
For every file upload attempt, there are some “headers” that are recurrent. Those are:
- File Name
- Content Type
- Magic Number
- File Content
- File Size
Those “headers”, if not validated correctly, can lead to denial of service, remote code execution, and potentially the exposure of the entire organization. Let’s explore how.
In most cases, files that are being uploaded serve some special purpose in terms of application’s functionality. For example, a CV document that is being uploaded is then inspected by the recruiting department to verify candidates. This means that only a handful of file formats are acceptable. Common technique to validate if the file format is correct is by checking it’s extension. On the server-side, this might be implemented using whitelists or blacklists.
Example 1:
if(preg_match(‘^.*\.(php|html|exe)$’)) return;
Bypass: the regex is vulnerable as it does not check for case-insensitive extensions. Users are able to upload shell.pHp.
Remediation: should use /i to make regex case-insensitive.
Example 2:
if(preg_match(‘^.*\.(pdf|xlsx)’)) process();
Bypass: the regex checks if the file contains the extension, not if it ends with it.
Remediation (applies to others as well): Don’t rely on the user’s input.\
Example 3:
/uploads/file.php.php.php.php.php.php(…).php.jpg
Bypass: If an attacker is able to guess the longest filename accepted, the server verifies the extension by blacklisting/whitelisting, and it truncates the name if it’s too long, it is possible to trick the server to save the file that ends with .php instead of .jpg.
Remediation (applies to others as well): Don’t rely on the user’s input.
Null-Byte Injection
The null-byte is a control character with the value zero. Some languages treat null bytes %00 as a terminator. Renaming the file to shell.php%00.jpg or shell.php\x00.jpg shall satisfy the upload page requirements because it ends with .jpg, but that file will be treated as PHP because everything after the null byte in the name will be ignored.
SVG Upload
SVG’s are just XML data. Using an XML attacker can achieve lots of vulnerabilities, for instance a stored XSS as below.
<svg width="100%" height="100%" onload="alert('XSS')"> <script type="textjavascript"><![CDARTA] // js here ]]></script> </svg>
If any application’s user is later able to download that file, javascript code will execute in his browser.
Remediation: Use backend-side well-tested package crafted for the sole purpose of validation SVGs.
Directory Traversal
Uploading a file named “../../../etc/passwd” might trick the server to override important server files.
Remediation: Don’t trust user-provided filename, store the filename randomly generated, and store the original name as metadata somewhere else.
Denial Of Service
It’s so basic that it sometimes ends up overlooked. Uploading too many big files concurrently might make the server stuck during the processing phase, or simply because it reached disk space limits.
Remediation: Check if the file size is of reasonable length.
Content-Type Bypass
Developers might validate if the filename is of the correct type using the Content-Type header.
Bypass: Content-Type header is trivial to spoof, and can be changed to anything the user wants, by intercepting the request using BurpSuite or ZAProxy and changing the header value on the fly to something the server accepts. The file format interpreted by the server will be whatever was in the file, as it most likely has no knowledge about the Content-Type after the request-response cycle.
Remediation: Don’t rely on the Content-Type header to validate the type of the file.
Magic Numbers
Developer might validate if the file is of the correct extension by checking if the file content starts with correct Magic Numbers. Magic numbers are the first few bytes of the file that identify what the type of that file is.
Bypass: Attacker might upload shell.php file, but start the file content as GIF89a. If Magic Number validation is in place, the server will accept it thinking that the file type is GIF.
Remediation: Don’t rely on Magic Numbers to validate file type.
Remote Code Execution Via Command Line Handled Files
The server might process the file using a system binary, launched, for example, using python’s subprocess module.
Bypass: If the filename is passed directly, an attacker might create a file called “file;whoami;pwd.jpg” and execute server commands.
Remediation: Store the file with randomly-generated value that is not going to end up by a valid command, and only then process it.
Image Tragick
If you are using third party software to process uploaded files, make sure that software is secure itself. A popular image processing library, ImageMagick is vulnerable to Remote Code Execution when processing user provided images.
Remediation: Don’t use ImageMagick if you don’t have to. If you do, sandbox your environment using technologies like chroot() or jail().
Final Thoughts
Though it seems simple, file upload can lead to serious security issues. It’s best not to rely on user input in anything that is going to touch the server eventually. Store user provided data in metadata instead, and name the file by yourself, so that it can’t be harmful. To validate if the file is what it is, use specialized software, or, if it’s not feasible, look at as many different factors as you can. Don’t trust third-party packages without any verification, because they too can have holes. Take a look at every factor you know, and if you’re using third-party packages, try to keep them up to date.