【问题标题】:Django ImageField validation (is it sufficient)?Django ImageField 验证(是否足够)?
【发布时间】:2011-08-11 20:32:54
【问题描述】:

我有很多用户上传的内容,我想验证上传的图像文件实际上不是恶意脚本。在 Django 文档中,它指出 ImageField:

“从 FileField 继承所有属性和方法,但也验证上传的对象是有效的图像。”

这完全准确吗?我读过压缩或以其他方式操作图像文件是一个很好的验证测试。我假设 PIL 会做这样的事情......

ImageField 会大大有助于保护我的图片上传安全吗?

【问题讨论】:

    标签: django security file-upload django-models


    【解决方案1】:

    Django 使用 PIL 验证通过表单上传的图像。 见https://code.djangoproject.com/browser/django/trunk/django/forms/fields.py#L519

    try:
        # load() is the only method that can spot a truncated JPEG,
        #  but it cannot be called sanely after verify()
        trial_image = Image.open(file)
        trial_image.load()
    
        # Since we're about to use the file again we have to reset the
        # file object if possible.
        if hasattr(file, 'reset'):
            file.reset()
    
        # verify() is the only method that can spot a corrupt PNG,
        #  but it must be called immediately after the constructor
        trial_image = Image.open(file)
        trial_image.verify()
     ...
     except Exception: # Python Imaging Library doesn't recognize it as an image
        raise ValidationError(self.error_messages['invalid_image'])
    

    PIL 文档中关于 verify() 的说明如下:

    尝试确定文件是否损坏,而不进行实际解码 图像数据。如果这种方法发现任何问题,它会提出合适的 例外。此方法仅适用于新打开的图像;如果 图像已加载,结果未定义。另外,如果你 使用此方法后需要加载图片,必须重新打开 图片文件。

    您还应该注意,ImageField 仅在使用表单上传时才被验证。如果您自己保存模型(例如使用某种下载脚本),则不会执行验证。

    【讨论】:

      【解决方案2】:

      另一个测试是使用file 命令。它检查文件中是否存在“幻数”以确定其类型。在我的系统上,file 包包括 libmagic 以及基于 ctypes 的包装器 /usr/lib64/python2.7/site-packages/magic.py。看起来你像这样使用它:

      import magic
      
      ms = magic.open(magic.MAGIC_NONE)
      ms.load()
      type =  ms.file("/path/to/some/file")
      print type
      
      f = file("/path/to/some/file", "r")
      buffer = f.read(4096)
      f.close()
      
      type = ms.buffer(buffer)
      print type
      
      ms.close()
      

      (来自here的代码。)


      关于您最初的问题:“卢克,请阅读源代码。”

      django/core/files/images.py:

      """
      Utility functions for handling images.
      
      Requires PIL, as you might imagine.
      """
      
      from django.core.files import File
      
      class ImageFile(File):
          """
          A mixin for use alongside django.core.files.base.File, which provides
          additional features for dealing with images.
          """
          def _get_width(self):
              return self._get_image_dimensions()[0]
          width = property(_get_width)
      
          def _get_height(self):
              return self._get_image_dimensions()[1]
          height = property(_get_height)
      
          def _get_image_dimensions(self):
              if not hasattr(self, '_dimensions_cache'):
                  close = self.closed
                  self.open()
                  self._dimensions_cache = get_image_dimensions(self, close=close)
              return self._dimensions_cache
      
      def get_image_dimensions(file_or_path, close=False):
          """
          Returns the (width, height) of an image, given an open file or a path.  Set
          'close' to True to close the file at the end if it is initially in an open
          state.
          """
          # Try to import PIL in either of the two ways it can end up installed.
          try:
              from PIL import ImageFile as PILImageFile
          except ImportError:
              import ImageFile as PILImageFile
      
          p = PILImageFile.Parser()
          if hasattr(file_or_path, 'read'):
              file = file_or_path
              file_pos = file.tell()
              file.seek(0)
          else:
              file = open(file_or_path, 'rb')
              close = True
          try:
              while 1:
                  data = file.read(1024)
                  if not data:
                      break
                  p.feed(data)
                  if p.image:
                      return p.image.size
              return None
          finally:
              if close:
                  file.close()
              else:
                  file.seek(file_pos)
      

      所以看起来它只是一次读取文件 1024 个字节,直到 PIL 说它是一个图像,然后停止。这显然不会检查整个文件的完整性,因此它实际上取决于您所说的“覆盖我的图像上传安全”的意思:非法数据可以附加到图像并通过您的站点传递。有人可以通过上传大量垃圾文件或非常大的文件来入侵您的网站。如果您不检查任何上传的字幕或对图像的上传文件名做出假设,您可能容易受到注入攻击。以此类推。

      【讨论】:

      • 嗨,迈克,我知道这会派上用场,但与 ImageField 结合使用时是否多余?显然 ImageField 执行某种文件类型验证
      • 感谢您的更新,您对限制是正确的。我有一些策略来处理防止大文件上传。注入攻击的可能性是我最关心的问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-06
      • 2011-04-14
      • 1970-01-01
      相关资源
      最近更新 更多