Проверка поля изображения Django (достаточно ли этого)?

#django #security #file-upload #django-models

#django #Безопасность #загрузка файла #django-модели

Вопрос:

У меня много загруженного пользователем контента, и я хочу проверить, что загруженные файлы изображений на самом деле не являются вредоносными скриптами. В документации Django указано, что поле изображения:

«Наследует все атрибуты и методы из FileField, но также проверяет, является ли загруженный объект допустимым изображением».

Это абсолютно точно? Я читал, что сжатие или иное манипулирование файлом изображения является хорошим тестом проверки. Я предполагаю, что PIL делает что-то вроде этого….

Будет ли ImageField иметь большое значение для обеспечения безопасности загрузки моего изображения?

Ответ №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():

Пытается определить, поврежден ли файл, без фактического декодирования данных изображения. Если этот метод обнаруживает какие-либо проблемы, он создает подходящие исключения. Этот метод работает только с недавно открытым изображением; если изображение уже было загружено, результат не определен. Кроме того, если вам нужно загрузить изображение после использования этого метода, вы должны повторно открыть файл изображения.

Вы также должны отметить, что поле изображения проверяется только при загрузке с использованием формы. Если вы сохраняете модель самостоятельно (например, используя какой-либо скрипт загрузки), проверка не выполняется.

Ответ №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()
  

(Код взят отсюда.)


Что касается вашего первоначального вопроса: «Прочитай источник, Люк».

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 не скажет, что это изображение, затем останавливается. Очевидно, что это не обеспечивает проверку целостности всего файла, так что это действительно зависит от того, что вы подразумеваете под «обеспечением безопасности загрузки моего изображения»: к изображению могут быть добавлены незаконные данные и переданы через ваш сайт. Кто-то может отключить ваш сайт, загрузив много мусора или действительно большой файл. Вы можете быть уязвимы для инъекционной атаки, если не проверяете какие-либо загруженные подписи или не делаете предположений о загруженном имени файла изображения. И так далее.

Комментарии:

1. Привет, Майк, я вижу, где это могло бы пригодиться, но является ли это избыточным в сочетании с ImageField? Очевидно, что ImageField выполняет какую-то проверку типа файла

2. Спасибо за обновление, и вы правы насчет ограничений. У меня есть несколько стратегий для предотвращения загрузки больших файлов. Меня больше всего беспокоят возможности инъекционной атаки.