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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/256172/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 22:06:04  来源:igfitidea点击:

What is the most secure method for uploading a file?

phpsecurityfile-upload

提问by Stephen Walcher

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

我正在尝试设置一个即插即用类型的上传脚本系列以供内部使用,设计人员可以将其复制到他们网站的结构中,修改一些变量,并在其上有一个随时可用的上传表单他们的网站。我们希望尽可能地限制我们的暴露(我们已经关闭了 fopen 和 shell 命令)。

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.

我在该网站上搜索了最后一个小时,发现了许多不同的答案,这些答案涉及依赖外部资源的特定方法。你们都认为什么是最好的纯脚本解决方案,它足够具体以用作可靠的保护方法?另外,如果可能的话,我想将语言限制为 PHP 或伪代码。

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.

编辑:我找到了我的答案(在下面发布),虽然它确实使用了 shell 命令 exec(),但如果您阻止上传脚本文件(此解决方案做得很好),您将不会遇到任何问题。

回答by inf3rno

  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_SIZEin your upload form, and set the maximum file sizeand counton the serveras 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 $_FILESand 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 injectionscaused 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 subdomainfor example http://static.example.comor at least a new directory outside of the public_html, for the uploaded files. This subdomainor directory should not execute any file. Set it in the server config, or set in a .htaccessfileby 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 typesent by the hacker. You should create a whitelistof allowed mime types. Allow images onlyif 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 contentfor example: HTMLin an image file can cause XSSby browsers with content sniffingvulnerability. When the corrupted content is a PHPcode, then it can be combined with an eval injectionvulnerability.

    $userContent = '../uploads/malicious.jpg';
    include('includes/'.$userContent);
    

    Try to avoid this, for example use a class autoloaderinstead of including php files manually...
    By handling the javascript injectionat first you have to turn off xssand content sniffingin the browsers. Content sniffingproblems 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 downloadby them, never includethem 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 exifdata. So you have to purge exiffrom images, if its content is not important to you. You can do that with Imagickor GD, but both of them requires repacking of the file. You can find an exiftoolas an alternative. I think the simplest way to clear exif, is loading images with GD, and save them as PNGwith highest quality. So the images won't lose quality, and the exiftag will be purged, because GDcannot handle it. Make this with images uploaded as PNGtoo...
    If you want to extract the exifdata, never use preg_replace()if the patternor replacementis 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.) Exifdata can be a problem if your site has an eval injectionvulnerability, 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 antivirto 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...

  1. 仅允许授权用户上传文件。您也可以添加验证码来阻止原始机器人。

  2. 首先,设定MAX_FILE_SIZE在上传表单,并设置最大的文件size,并count在服务器上也是如此。

    ini_set('post_max_size', '40M'); //or bigger by multiple files
    ini_set('upload_max_filesize', '40M');
    ini_set('max_file_uploads', 10);
    

    通过上传的文件进行大小检查:

    if ($fileInput['size'] > $sizeLimit)
        ; //handle size error here
    
  3. 您应该使用$_FILESmove_uploaded_file()将上传的文件放入正确的目录中,或者如果要处理它,请使用is_uploaded_file(). (这些函数的存在是为了防止由.引起的文件名注入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);
    

    始终生成一个随机 id 而不是使用原始文件名

  4. 为上传的文件创建一个新的子域,例如http://static.example.com或至少在public_html,之外的新目录。此子域或目录不应执行任何文件。在服务器配置中设置它,或者.htaccess目录中设置一个文件

        SetHandler none
        SetHandler default-handler
        Options -ExecCGI
        php_flag engine off
    

    将其设置chmod()为好。

        $noExecMode = 0644;
        chmod($uploadedFile, $noExecMode);
    

    也用于chmod()新上传的文件并将其设置在目录中。

  5. 您应该检查黑客发送mime 类型。您应该创建一个允许mime 类型白名单当不需要任何其他格式时才允许图像。任何其他格式都是安全威胁。图片太多,但至少我们有工具来处理他们... 的损坏的内容,例如:HTML的图像文件可能导致XSS通过与浏览器的内容嗅探漏洞。当损坏的内容是PHP代码时,则可以与eval 注入漏洞结合使用。

    $userContent = '../uploads/malicious.jpg';
    include('includes/'.$userContent);
    

    尽量避免这种情况,例如使用 aclass autoloader而不是手动包含 php 文件...首先
    通过处理javascript 注入,您必须关闭浏览器中的xss内容嗅探内容嗅探问题是较老的msie 的典型问题,我认为其他浏览器可以很好地过滤它们。无论如何,您可以使用一堆标题来防止这些问题。(并非每个浏览器都完全支持,但这是您在客户端可以做的最好的事情。)

    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}
    

    您可以使用 来检查文件是否已损坏Imagick identify,但这并不意味着完全保护。

    try {
        $uploadedImage = new Imagick($uploadedFile);
        $attributes = $uploadedImage->identifyImage();
        $format = $image->getImageFormat();
        var_dump($attributes, $format);
    } catch (ImagickException $exception) {
        //handle damaged or corrupted images
    }
    

    如果您想为其他mime 类型提供服务,则应始终强制它们下载,切勿它们包含在网页中,除非您真的知道自己在做什么...

    X-Download-Options: noopen
    Content-Disposition: attachment; filename=untrustedfile.html
    
  6. 可能有包含代码的有效图像文件,例如在exif数据中。所以你必须从 images 中清除exif,如果它的内容对你不重要。您可以使用Imagick或来做到这一点GD,但它们都需要重新打包文件。你可以找到一个exiftool作为替代。我认为清除exif的最简单方法是使用GD加载图像,并将它们保存为最高质量的PNG。因此图像不会丢失质量,并且exif标签将被清除,因为GD无法处理它。也用上传为PNG 的图像制作这个...
    如果您想提取exif数据,请不要使用preg_replace()if the patternor replacementis from the user,因为这会导致eval 注入...如果需要,请使用preg_replace_callback()代替eval regex flag。(复制粘贴代码中的常见错误。) 如果您的站点存在eval 注入漏洞,例如,如果您在某处使用,则Exif数据可能是一个问题。include($userInput)

  7. include()require()如果您想控制访问,请永远不要使用,通过上传的文件,将它们用作静态或使用file_get_contents()readfile(),或任何其他文件读取功能。
    它很少可用,但我认为将X-Sendfile: {filename}标头与sendfile apache 模块一起使用的最佳方法。通过标头,切勿在未经验证或清理的情况下使用用户输入,因为这会导致HTTP 标头注入
    如果您不需要访问控制(意思是:只有授权用户才能看到上传的文件),请使用您的网络服务器提供文件。它要快得多...

  8. 如果您有上传的文件,请使用防病毒软件检查上传的文件。

  9. 始终使用组合保护,而不仅仅是单一方法。突破你的防御会更难......

回答by andy.gurin

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.

恕我直言,最好的解决方案是将包含上传文件的目录放在“网络”环境之外,并使用脚本使其可下载。这样,即使有人上传了脚本,也无法通过浏览器调用来执行,也不必检查上传文件的类型。

回答by Vinko Vrsalovic

Use and configure Hardened-PHPcreate a plain script using move_uploaded_fileand the $_FILES superglobal. The simplest the script, the safest it will be (at least, as safe as the running PHP version itself)

使用和配置Hardened-PHP创建一个使用move_uploaded_file$_FILES superglobal的普通脚本。脚本越简单,就越安全(至少,与运行的 PHP 版本本身一样安全)