在php和htaccess中安全上传图片的方法

7

我在互联网上找到了以下代码,用于php中的安全图片上传。

我想知道它是否覆盖了所有可能的图片上传攻击方式。

define('MAX_SIZE_EXCEDED', 101);
define('UPLOAD_FAILED', 102);
define('NO_UPLOAD', 103);
define('NOT_IMAGE', 104);
define('INVALID_IMAGE', 105);
define('NONEXISTANT_PATH', 106);

class ImgUploader
{
  var $tmp_name;
  var $name;
  var $size;
  var $type;
  var $error;
  var $width_orig;
  var $height_orig;
  var $num_type;
  var $errorCode = 0;
    var $allow_types = array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG);

  function __construct($fileArray)
  {
    foreach($fileArray as $key => $value)
    {
      $this->$key = $value;
    }
    if($this->error > 0)
    {
      switch ($this->error)
      {
        case 1: $this->errorCode = MAX_SIZE_EXCEDED; break;
        case 2: $this->errorCode = MAX_SIZE_EXCEDED; break;
        case 3: $this->errorCode = UPLOAD_FAILED; break;
        case 4: $this->errorCode = NO_UPLOAD; break;
      }
    }
    if($this->errorCode == 0)
    {
      $this->secure();
    }
  }

  function secure()
  {
    //$this->num_type = exif_imagetype($this->tmp_name);
    @list($this->width_orig, $this->height_orig, $this->num_type) = getimagesize($this->tmp_name);

    if(filesize($this->tmp_name) > 1024*1024*1024*5) // allows for five megabytes.  Change this number if need be.
    {
      $this->errorCode = MAX_SIZE_EXCEDED;
      return false;
    }

    if (!$this->num_type)
    {
      $this->errorCode = NOT_IMAGE;
        return false;
    }
    if(!in_array($this->num_type, $this->allow_types))
    {
      $this->errorCode = INVALID_IMAGE;
      return false;
    }
  }

  function getError()
  {
    return $this->errorCode;
  }

  function upload_unscaled($folder, $name)
  {
    return $this->upload($folder, $name, "0", "0");
  }

  function upload($folder, $name, $width, $height, $scaleUp = false)
  {
    // $folder is location to be saved
    // $name is name of file, without file extention
    // $width is desired max width
    // $height is desired max height

    if($this->errorCode > 0)
      return false;

    // deal with sizing
    // if image is small enough to not scale, or upload_unscaled() is called, don't scale
    if((!$scaleUp && ($width > $this->width_orig && $height > $this->height_orig)) || ($width === "0" && $height === "0"))
    {
      $width = $this->width_orig;
      $height = $this->height_orig;
    }
    else
    {
      // if height diff is less than width dif, calc height
      if(($this->height_orig - $height) <= ($this->width_orig - $width))
        $height = ($width / $this->width_orig) * $this->height_orig;
      else
        $width = ($height / $this->height_orig) * $this->width_orig;
    }

    // Resample
    switch($this->num_type)
    {
      case IMAGETYPE_GIF: $image_o = imagecreatefromgif($this->tmp_name); $ext = '.gif'; break;
      case IMAGETYPE_JPEG: $image_o = imagecreatefromjpeg($this->tmp_name); $ext = '.jpg'; break;
      case IMAGETYPE_PNG: $image_o = imagecreatefrompng($this->tmp_name); $ext = '.png'; break;
    }

    $filepath = $folder.(substr($folder,-1) != '/' ? '/' : '');
    if(is_dir($_SERVER['DOCUMENT_ROOT'].$filepath))
      $filepath .= $name.$ext;
    else
    {
      $this->errorCode = NONEXISTANT_PATH;
      imagedestroy($image_o);
      return false;
    }

    $image_r = imagecreatetruecolor($width, $height);
    imagecopyresampled($image_r, $image_o, 0, 0, 0, 0, $width, $height, $this->width_orig, $this->height_orig);

    switch($this->num_type)
    {
      case IMAGETYPE_GIF: imagegif($image_r, $_SERVER['DOCUMENT_ROOT'].$filepath); break;
      case IMAGETYPE_JPEG: imagejpeg($image_r, $_SERVER['DOCUMENT_ROOT'].$filepath); break;
      case IMAGETYPE_PNG: imagepng($image_r, $_SERVER['DOCUMENT_ROOT'].$filepath); break;
    }

    imagedestroy($image_o);
    imagedestroy($image_r);

    return '/'.$filepath;
  }
}

我还在“images”文件夹中有一个.htaccess文件,它关闭了文件脚本,这样就不会有人在照片文件夹中执行脚本。
<Files ^(*.jpg)>
order deny,allow
deny from all
</Files>
Options -Indexes
Options -ExecCGI 
AddHandler cgi-script .php .php3 .php4 .php5 .phtml .pl .py .jsp .asp .htm .shtml .sh .cgi

这样足够保证安全吗?还是我需要采取其他措施来加强我的代码和网站的安全性。

你自己试过这段代码吗?你有使用样例吗? - Abu Romaïssae
5个回答

7
我想先提醒您,用Apache的mod_mime检查文件类型非常重要。否则,我可以发送一个HTTP请求,声称自己是JPEG文件,然后您只会信任文件扩展名,如“没错!你有.jpg扩展名”,但实际上我可以注入PHP源代码而不是JPEG数据,之后就可以在您的服务器上执行该PHP源代码。
另一件事是始终强制Apache不要运行这些图像类型。您可以使用ForceType指令来实现。
<FilesMatch "(?i)\.jpe?g$">
    ForceType image/jpeg
</FilesMatch>

编辑:

我没有看到底部的内容。关闭文件脚本绝对有帮助。


3

这段代码安全吗?

PHP

简而言之,不一定!该代码存在一个致命缺陷,即没有涵盖所有情况的文件MIME类型检测,因此可能会带来很多问题。

除此漏洞外,该代码还算相对安全。

Apache

伪造HTTP请求以看起来像JPEG是一件非常容易的事情,虽然PHP可以处理这个问题,但有时确保通过所有途径解决漏洞更好。使用Apache中的MOD_MIME模块可以轻松解决此问题,如下所述。

HTTPS - 可能无关紧要

如果您担心这些文件在客户端和服务器之间被嗅探,则网站上使用SSL证书是绝对必要的安全措施。这将加密客户端和服务器之间的所有数据。

但是,如果您只是担心服务器端的问题,那么这并不是真正必要的,尽管建议使用。

可能的解决方案

finfo()函数

finfo()函数将返回文件的MIME类型,从而指示它是否为JPEG并可以上传。

有关此功能的更多详细信息,请请看这里

上传替代方案

个人而言,我更喜欢使用Colin Verot Upload Class。该类非常易于使用,涵盖了所有潜在的安全问题,具有广泛的GD库扩展功能,并且不断得到维护。

访问Colin Verot的网站这里下载并开始使用该类。

Apache MOD_MIME

Apache MOD_MIME模块将强制检查发送到服务器的文件的MIME类型。

这里查看更多信息。


1

这段代码看起来很好,上面的答案也不错。我想再补充一点关于安全方面的内容:

  • 你应该将文件存储在文档根目录之外的目录中(无法通过http请求访问),并通过一个检查适当授权的脚本来提供它们。

0

由于您在服务器上关闭了所有文件解释,唯一剩下的就是文件名。如果您生成自己的名称或者过滤用户提供的名称,那么您就没有什么可担心的了。

嗯...几乎没有什么。GD库中可能存在安全错误,因此在调整大小期间使用恶意图像可能会导致某些不良后果。但这不是您可以从PHP处理的事情,所以请保持服务器更新。


-1

据我所知,该表单(在整个网络上都被使用)要求用户实现自己的安全性。它看起来很漂亮,但任何合格的开发人员都可以使用jQuery。主要优先事项应始终是安全性。 - Amy Neville
该框架包括易于配置的文件类型限制,路径非常灵活,允许您将文件存储在任何受限目录甚至不在Web根目录中的目录中。这简化了大部分安全文件上传工作。 - colonelclick

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