2011-04-24 130 views
5

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

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

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

威尔的ImageField去朝着覆盖我的图片上传安全很长的路要走?

回答

1

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

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


至于你原来的问题: “阅读源代码,卢克。”

的Django /核心/文件/ 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说,这是一个图像,然后停止。这显然不能完整地检查整个文件,所以它实际上取决于“覆盖我的图像上传安全性”的含义:非法数据可以附加到图像并通过您的网站传递。有人可以通过上传很多垃圾或一个非常大的文件来阻止您的网站。如果您没有检查任何上传的标题或对图像上传的文件名做出假设,则可能容易受到注入攻击。等等。

+0

嗨迈克,我能看到这会派上用场,但它是多余的,当用的ImageField结合起来呢?很明显,ImageField执行某种类型的文件类型验证 – Ben 2011-04-24 13:38:12

+0

感谢您的更新,并且您对这些限制是正确的。我有一些策略来处理防止大文件上传的问题。注射攻击的可能性是我最担心的问题。 – Ben 2011-04-24 17:33:57

7

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文档指出以下有关验证():

尝试,以确定是否该文件被破坏,而无需实际解码 的图像数据。如果此方法发现任何问题,则会引发合适的例外情况。此方法仅适用于新打开的图像;如果 图像已被加载,则结果未定义。另外,如果您在使用此方法后需要加载图像 ,则必须重新打开 图像文件。

你也应该注意到的ImageField使用形式上传时才会验证。如果您自己保存模型(例如使用某种下载脚本),则不会执行验证。