File upload is becoming a more and more essential part of any web application. Imagine an application that is usable and does not have some kind of uploading... you can count them on the fingers of one hand. Every developer should be able to defend against bugs and malicious files in a way to keep the application and the users safe.
In short, following these policies should suffice against any known attacks that could be done through file uploading:
- List allowed extensions. Only allow safe and critical extensions for business functionality.
- Ensure that uploaded files CANNOT be executed as code!
- Ensure that input validation is applied before validating the extensions.
- Validate the file type, don't trust the Content-Type header as it can be spoofed very easily.
- Change the filename to something generated by the application. (like a custom hash function 'a.txt' -> jd21j3jjd213jj3321.txt)
- Set a filename length limit. Restrict the allowed characters if possible. (Try cleaning up the file's name)
- Set a file size limit.
- Only allow authorized users to upload files.
- Store the files on a different server. If that's not possible, store them outside of the webroot.
- In the case of public access to the files, use a handler that gets mapped to filenames inside the application (some id -> file.extension)
- Run the file through antivirus or a sandbox if available to validate that it doesn't contain malicious data.
- Ensure that any libraries used are securely configured and kept up to date.
- Protect the file upload from CSRF attacks. (If you have implemented some sort of
get-this-file-from-this-url.
)
The zombie that's not in your house can't hurt you!
Jokes aside, one of the most important approaches to a secure file uploading functionality is ensuring that your web server treats the uploaded files as inert rather than executable objects. In this sense, you may opt for the alternative of storing the uploaded files into a CDN(Content Delivery Network) such as Cloudflare; you have maybe guessed from my zombie quote that by doing this a developer basically unloads the security burden to the third party that they are working with, such that the files are always stored securely in this manner.
Of course, using a CDN may not be your best pick, and thus you may opt for one of the other alternatives, such as cloud-based storage(Amazon S3, Google Storage Bucket, etc), or even dedicated CMS(although this comes with the extra configuration and attention headache).
You may not opt for CDNs or any of the alternatives listed above. Should that be the case, and you really have to upload files to your "local" disk, here's what you have to do:
- Renaming the files as you upload them: creating a custom or using a default hashing function for the filenames is a good principle so that it is harder to guess where exactly the files have been saved.
- Write to disk without executable permissions: this is **** a necessary step towards securing your uploading process. There are countless scripts or executable files that an attacker may leverage, and thus you simply have to remove this kind of permissions from the files that get uploaded.
{% tabs %} {% tab title="Python" %}
import os
file_f = os.open("/path/to/your/file", os.O_WRONLY | os.O_CREAT, 0o600)
with os.fdopen(open(file_f, "wb")) as file_handling:
file_handling.write(...)
{% endtab %}
{% tab title="Node.js" %}
var chmodr = require('chmodr');
chmodr('/path/to/entire/folder', 0o600, (err) => {
if (err) {
console.log('Failed to execute chmod', err);
} else {
console.log('Success');
}
});
{% endtab %} {% endtabs %}
- Validate the file extensions: ensure that the validation occurs after decoding the file name, and that a proper filter is set in place in order to avoid certain known bypasses, such as the following:
- Double extensions, e.g.
.jpg.php
, where it circumvents easily the regex\.jpg.
- Null bytes, e.g.
.php%00.jpg
, where.jpg
gets truncated and.php
becomes the new extension. - Generic bad regex that isn't properly tested and reviewed. Refrain from building your own logic unless you have enough knowledge on this topic.
- Double extensions, e.g.
- File content verification: As a file's content may contain malicious or inappropriate data, a developer can always extra-validate it, based on the expected file type:
- For images, it is worth reading these two posts: Inject Executable, malicious code into PNG and How can I be protected from picture vulnerabilities. Image rewriting is one of the common techniques that in theory destroy any kind of malicious content that could be injected into the image, and this is typically done through randomization.
- Uploading ZIP files is totally not recommended, since they can literally contain any type of files, and thus allow for limitless.
While the fundamental aspects of file uploading remain the same, developers have for good reason started looking towards ways of "incorporating" file storage systems, that are easy, secure and overall convenient to use.
{% hint style="info" %} You can find more details about this topic here: