Question

The company I work for has recently been hit with many header injection and file upload exploits on the sites we host and while we have fixed the problem with respect to header injection attacks, we have yet to get the upload exploits under control.

I'm trying to set up a plug-and-play-type series of upload scripts to use in-house that a designer can copy into their site's structure, modify a few variables, and have a ready-to-go upload form on their site. We're looking to limit our exposure as much as possible (we've already shut down fopen and shell commands).

I've searched the site for the last hour and found many different answers dealing with specific methods that rely on outside sources. What do you all think is the best script-only solution that is specific enough to use as a reliable method of protection? Also, I'd like to keep the language limited to PHP or pseudo-code if possible.

Edit: I've found my answer (posted below) and, while it does make use of the shell command exec(), if you block script files from being uploaded (which this solution does very well), you won't run into any problems.

Was it helpful?

Solution

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.

OTHER TIPS

  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.'/'.$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...

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)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top