What is the most secure method for uploading a file?

  1. Allow only authorized users to upload a file. You can add a captcha as well to hinder primitive bots.

  2. First of all, set the MAX_FILE_SIZE in your upload form, and set the maximum file size and count 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
    
  3. You should use $_FILES and move_uploaded_file() to put your uploaded files into the right directory, or if you want to process it, then check with is_uploaded_file(). (These functions exist to prevent file name injections caused by register_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."https://stackoverflow.com/".$name.'.'.$extension);
    

    Always generate a random id instead of using the original file name.

  4. 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.

  5. 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
    
  6. 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 or GD, but both of them requires repacking of the file. You can find an exiftool 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 use preg_replace() if the pattern or replacement is from the user, because that will lead to an eval injection… Use preg_replace_callback() instead of the eval 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 use include($userInput) somewhere.

  7. Never ever use include(), require() by uploaded files, serve them as static or use file_get_contents() or readfile(), or any other file reading function, if you want to control access.
    It is rarely available, but I think the best approach to use the X-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…

  8. Use an antivir to check the uploaded files, if you have one.

  9. Always use a combined protection, not just a single approach. It will be harder to breach your defenses…

Leave a Comment

tech