What is the most secure method for uploading a file?
Solution 1:
Allow only authorized users to upload a file. You can add a captcha as well to hinder primitive bots.
-
First of all, set the
MAX_FILE_SIZE
in your upload form, and set the maximum filesize
andcount
on the server as well.ini_set('post_max_size', '40M'); //or bigger by multiple files ini_set('upload_max_filesize', '40M'); ini_set('max_file_uploads', 10);
Do size check by the uploaded files:
if ($fileInput['size'] > $sizeLimit) ; //handle size error here
-
You should use
$_FILES
andmove_uploaded_file()
to put your uploaded files into the right directory, or if you want to process it, then check withis_uploaded_file()
. (These functions exist to prevent file name injections caused byregister_globals
.)$uploadStoragePath = '/file_storage'; $fileInput = $_FILES['image']; if ($fileInput['error'] != UPLOAD_ERR_OK) ; //handle upload error here, see http://php.net/manual/en/features.file-upload.errors.php //size check here $temporaryName = $fileInput['tmp_name']; $extension = pathinfo($fileInput['name'], PATHINFO_EXTENSION); //mime check, chmod, etc. here $name = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM)); //true random id move_uploaded_file($temporaryName, $uploadStoragePath.'/'.$name.'.'.$extension);
Always generate a random id instead of using the original file name.
-
Create a new subdomain for example http://static.example.com or at least a new directory outside of the
public_html
, for the uploaded files. This subdomain or directory should not execute any file. Set it in the server config, or set in a.htaccess
file by the directory.SetHandler none SetHandler default-handler Options -ExecCGI php_flag engine off
Set it with
chmod()
as well.$noExecMode = 0644; chmod($uploadedFile, $noExecMode);
Use
chmod()
on the newly uploaded files too and set it on the directory. -
You should check the mime type sent by the hacker. You should create a whitelist of allowed mime types. Allow images only if any other format is not necessary. Any other format is a security threat. Images too, but at least we have tools to handle them...
The corrupted content for example: HTML in an image file can cause XSS by browsers with content sniffing vulnerability. When the corrupted content is a PHP code, then it can be combined with an eval injection vulnerability.$userContent = '../uploads/malicious.jpg'; include('includes/'.$userContent);
Try to avoid this, for example use a
class autoloader
instead of including php files manually...
By handling the javascript injection at first you have to turn off xss and content sniffing in the browsers. Content sniffing problems are typical by older msie, I think the other browsers filter them pretty well. Anyways you can prevent these problems with a bunch of headers. (Not fully supported by every browser, but that's the best you can do on client side.)Strict-Transport-Security: max-age={your-max-age} X-Content-Type-Options: nosniff X-Frame-Options: deny X-XSS-Protection: 1; mode=block Content-Security-Policy: {your-security-policy}
You can check if a file is corrupted with
Imagick identify
, but that does not mean a complete protection.try { $uploadedImage = new Imagick($uploadedFile); $attributes = $uploadedImage->identifyImage(); $format = $image->getImageFormat(); var_dump($attributes, $format); } catch (ImagickException $exception) { //handle damaged or corrupted images }
If you want to serve other mime types, you should always force download by them, never include them into webpages, unless you really know what you are doing...
X-Download-Options: noopen Content-Disposition: attachment; filename=untrustedfile.html
It is possible to have valid image files with code inside them, for example in exif data. So you have to purge exif from images, if its content is not important to you. You can do that with
Imagick
orGD
, but both of them requires repacking of the file. You can find anexiftool
as an alternative. I think the simplest way to clear exif, is loading images with GD, and save them as PNG with highest quality. So the images won't lose quality, and the exif tag will be purged, because GD cannot handle it. Make this with images uploaded as PNG too...
If you want to extract the exif data, never usepreg_replace()
if thepattern
orreplacement
is from the user, because that will lead to an eval injection... Usepreg_replace_callback()
instead of theeval regex flag
, if necessary. (Common mistake in copy paste codes.) Exif data can be a problem if your site has an eval injection vulnerability, for example if you useinclude($userInput)
somewhere.Never ever use
include()
,require()
by uploaded files, serve them as static or usefile_get_contents()
orreadfile()
, or any other file reading function, if you want to control access.
It is rarely available, but I think the best approach to use theX-Sendfile: {filename}
headers with the sendfile apache module. By the headers, never use user input without validation or sanitization, because that will lead to HTTP header injection.
If you don't need access control (means: only authorized users can see the uploaded files), then serve the files with your webserver. It is much faster...Use an antivir to check the uploaded files, if you have one.
Always use a combined protection, not just a single approach. It will be harder to breach your defenses...
Solution 2:
The best solution, IMHO, is to put the directory containing the uploaded files outside of the "web" environment and use a script to make them downloadable. In this way, even if somebody uploads a script it can not be executed by calling it from the browser and you don't have to check the type of the uploaded file.
Solution 3:
Use and configure Hardened-PHP create a plain script using move_uploaded_file and the $_FILES superglobal. The simplest the script, the safest it will be (at least, as safe as the running PHP version itself)