diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index d538f94bf..8cc6a2ef4 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -1,7 +1,10 @@ +import io import json import urllib.parse from pathlib import Path +from PIL import Image as PillowImage + from django.conf import settings from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.postgres.fields import ArrayField @@ -741,6 +744,31 @@ class ImageAttachment(ChangeLoggedModel): _("Image attachments cannot be assigned to this object type ({type}).").format(type=self.object_type) ) + # Re-encode the image through Pillow to strip any embedded non-image + # payloads (e.g. polyglot HTML/PNG files). This must be done after the + # ImageField's own Pillow dimension check has already run. + if self.image and hasattr(self.image, 'file'): + try: + self.image.file.seek(0) + img = PillowImage.open(self.image.file) + img_format = img.format or 'PNG' + # Validate extension against allowed formats + ext = Path(self.image.name).suffix.lstrip('.').lower() + if ext not in IMAGE_ATTACHMENT_IMAGE_FORMATS: + raise ValidationError( + _("Unsupported image format: {ext}").format(ext=ext) + ) + # Re-encode to a new buffer; this strips any non-image trailer data + buf = io.BytesIO() + img.save(buf, format=img_format) + buf.seek(0) + self.image.file = buf + self.image.file.seek(0) + except ValidationError: + raise + except Exception: + raise ValidationError(_("Unable to process the uploaded image.")) + def delete(self, *args, **kwargs): _name = self.image.name