上传文件的最安全方法是什么?

41
我所在公司的网站最近遭受了许多标题注入和文件上传攻击,虽然我们已经解决了标题注入攻击问题,但我们仍然无法控制上传漏洞。
我正在尝试设置一系列即插即用的上传脚本供设计师复制到他们网站的结构中,修改一些变量,并在他们的网站上拥有一个立即可用的上传表单。我们正在尽可能地限制曝光(我们已经关闭了fopen和shell命令)。
我在网站上搜索了一个小时,找到了许多不同的答案,涉及特定的方法,这些方法依赖于外部源。你们认为哪种仅限于脚本的解决方案是最好的,可以作为保护的可靠方法?此外,如果可能的话,我想将语言限制在PHP或伪代码中。
编辑:我已经找到了我的答案(如下所述),虽然它确实使用了shell命令exec(),但如果阻止上传脚本文件(这个解决方案做得很好),就不会遇到任何问题。
3个回答

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


感谢您的评论。我忘记了这篇文章,但是您提出了一些好的建议,我会记住的。 - Stephen Walcher
@inf3rno,你能给我发送一个指导示例的链接吗?该示例展示了所有提到的功能的实现。 - Abida

40

我个人认为最好的解决方案是将包含上传文件的目录放在“web”环境之外,并使用脚本使它们可下载。这样,即使有人上传了一个脚本,也不能通过从浏览器中调用它来执行它,而且您也不必检查上传文件的类型。


3
是的,将该目录放置在一个特殊的noexec挂载分区中会更好。 - Vinko Vrsalovic
3
将分区设置为 nosuid。 - Eric Hogue
我同意这可能是最安全的方法,但对于我的特定情况,我下面发布的答案是适合我的。 - Stephen Walcher
1
@VinkoVrsalovic,noexec选项不是用于安全性;来自man页面:“”“注意:此选项不是作为安全功能设计的,不能保证它将防止恶意代码执行;”“ - Good Person
@GoodPerson 虽然它不是设计成这样的,但作为深度防御措施,它肯定是一种有用的方法。请查看http://serverfault.com/questions/72356/how-useful-is-mounting-tmp-noexec - Vinko Vrsalovic

1

Hardened-PHP.net不可用,当前页面为Hardened-PHP https://www.cloudlinux.com/hardenedphp - Michel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接