VYPR
High severityNVD Advisory· Published Sep 29, 2023· Updated Sep 23, 2024

CVE-2023-44464

CVE-2023-44464

Description

pretix before 2023.7.2 allows Pillow to parse EPS files.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

pretix before 2023.7.2 allows uploading EPS files disguised as PNG, triggering Pillow/Ghostscript parsing that can lead to remote code execution.

Vulnerability

pretix, an open-source ticket sales platform, failed to validate that uploaded image files actually match the allowed formats (e.g., PNG, JPEG). By renaming an EPS file to have a .png extension, an attacker could bypass the extension check and have the file parsed by Pillow, which in turn calls Ghostscript to handle EPS data [1][4].

Exploitation

An attacker with permission to upload images—such as event organizers or, if the event enables customer file uploads via the Questions feature, any user—can upload a malicious EPS file. No special authentication is needed beyond the ability to upload images. The processing of the file via Ghostscript can then exploit known vulnerabilities in Ghostscript to achieve remote code execution [2][4].

Impact

Successful exploitation allows an attacker to execute arbitrary code on the server running pretix. This is a critical vulnerability (CVSS score not explicitly given but described as CRITICAL) and can lead to full compromise of the application and underlying system [4].

Mitigation

The vulnerability is patched in pretix version 2023.7.2 and later. Users of the pretix Hosted service had the fix applied automatically. All self-hosted installations should update immediately to the latest version. No workaround is available [1][4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
pretixPyPI
< 2023.7.22023.7.2

Affected products

2

Patches

1
8583bfb7d972

[SECURITY] Do not allow Pillow to parse EPS files

https://github.com/pretix/pretixRaphael MichelSep 11, 2023via ghsa
13 files changed · +60 27
  • src/pretix/api/views/order.py+2 1 modified
    @@ -26,6 +26,7 @@
     from zoneinfo import ZoneInfo
     
     import django_filters
    +from django.conf import settings
     from django.db import transaction
     from django.db.models import (
         Exists, F, OuterRef, Prefetch, Q, Subquery, prefetch_related_objects,
    @@ -1191,7 +1192,7 @@ def pdf_image(self, request, key, **kwargs):
                 ftype, ignored = mimetypes.guess_type(image_file.name)
                 extension = os.path.basename(image_file.name).split('.')[-1]
             else:
    -            img = Image.open(image_file)
    +            img = Image.open(image_file, formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
                 ftype = Image.MIME[img.format]
                 extensions = {
                     'GIF': 'gif', 'TIFF': 'tif', 'BMP': 'bmp', 'JPEG': 'jpg', 'PNG': 'png'
    
  • src/pretix/base/forms/questions.py+4 8 modified
    @@ -500,14 +500,14 @@ def to_python(self, data):
                     file = BytesIO(data['content'])
     
             try:
    -            image = Image.open(file)
    +            image = Image.open(file, formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
                 # verify() must be called immediately after the constructor.
                 image.verify()
     
                 # We want to do more than just verify(), so we need to re-open the file
                 if hasattr(file, 'seek'):
                     file.seek(0)
    -            image = Image.open(file)
    +            image = Image.open(file, formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
     
                 # load() is a potential DoS vector (see Django bug #18520), so we verify the size first
                 if image.width > 10_000 or image.height > 10_000:
    @@ -566,7 +566,7 @@ def to_python(self, data):
             return f
     
         def __init__(self, *args, **kwargs):
    -        kwargs.setdefault('ext_whitelist', (".png", ".jpg", ".jpeg", ".jfif", ".tif", ".tiff", ".bmp"))
    +        kwargs.setdefault('ext_whitelist', settings.FILE_UPLOAD_EXTENSIONS_QUESTION_IMAGE)
             kwargs.setdefault('max_size', settings.FILE_UPLOAD_MAX_SIZE_IMAGE)
             super().__init__(*args, **kwargs)
     
    @@ -826,11 +826,7 @@ def __init__(self, *args, **kwargs):
                             help_text=help_text,
                             initial=initial.file if initial else None,
                             widget=UploadedFileWidget(position=pos, event=event, answer=initial),
    -                        ext_whitelist=(
    -                            ".png", ".jpg", ".gif", ".jpeg", ".pdf", ".txt", ".docx", ".gif", ".svg",
    -                            ".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages",
    -                            ".bmp", ".tif", ".tiff"
    -                        ),
    +                        ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_OTHER,
                             max_size=settings.FILE_UPLOAD_MAX_SIZE_OTHER,
                         )
                 elif q.type == Question.TYPE_DATE:
    
  • src/pretix/base/models/orders.py+1 1 modified
    @@ -1246,7 +1246,7 @@ def frontend_file_url(self):
     
         @property
         def is_image(self):
    -        return any(self.file.name.lower().endswith(e) for e in ('.jpg', '.png', '.gif', '.tiff', '.bmp', '.jpeg'))
    +        return any(self.file.name.lower().endswith(e) for e in settings.FILE_UPLOAD_EXTENSIONS_QUESTION_IMAGE)
     
         @property
         def file_name(self):
    
  • src/pretix/base/pdf.py+1 1 modified
    @@ -521,7 +521,7 @@ def get_answer(op, order, event, question_id, etag):
             else:
                 a = op.answers.filter(question_id=question_id).first() or a
     
    -        if not a or not a.file or not any(a.file.name.lower().endswith(e) for e in (".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tif", ".tiff")):
    +        if not a or not a.file or not any(a.file.name.lower().endswith(e) for e in settings.FILE_UPLOAD_EXTENSIONS_QUESTION_IMAGE):
                 return None
             else:
                 if etag:
    
  • src/pretix/base/settings.py+4 4 modified
    @@ -2793,7 +2793,7 @@ def unserialize(cls, s):
             'form_class': ExtFileField,
             'form_kwargs': dict(
                 label=_('Header image'),
    -            ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"),
    +            ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
                 max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
                 help_text=_('If you provide a logo image, we will by default not show your event name and date '
                             'in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You '
    @@ -2836,7 +2836,7 @@ def unserialize(cls, s):
             'form_class': ExtFileField,
             'form_kwargs': dict(
                 label=_('Header image'),
    -            ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"),
    +            ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
                 max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
                 help_text=_('If you provide a logo image, we will by default not show your organization name '
                             'in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You '
    @@ -2876,7 +2876,7 @@ def unserialize(cls, s):
             'form_class': ExtFileField,
             'form_kwargs': dict(
                 label=_('Social media image'),
    -            ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"),
    +            ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
                 max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
                 help_text=_('This picture will be used as a preview if you post links to your ticket shop on social media. '
                             'Facebook advises to use a picture size of 1200 x 630 pixels, however some platforms like '
    @@ -2897,7 +2897,7 @@ def unserialize(cls, s):
             'form_class': ExtFileField,
             'form_kwargs': dict(
                 label=_('Logo image'),
    -            ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"),
    +            ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
                 required=False,
                 max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
                 help_text=_('We will show your logo with a maximal height and width of 2.5 cm.')
    
  • src/pretix/control/forms/__init__.py+1 1 modified
    @@ -127,7 +127,7 @@ def name(self):
     
             @property
             def is_img(self):
    -            return any(self.file.name.lower().endswith(e) for e in ('.jpg', '.jpeg', '.png', '.gif'))
    +            return any(self.file.name.lower().endswith(e) for e in settings.FILE_UPLOAD_EXTENSIONS_IMAGE)
     
             def __str__(self):
                 if hasattr(self.file, 'display_name'):
    
  • src/pretix/control/forms/organizer.py+2 2 modified
    @@ -420,7 +420,7 @@ class OrganizerSettingsForm(SettingsForm):
     
         organizer_logo_image = ExtFileField(
             label=_('Header image'),
    -        ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"),
    +        ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_IMAGE,
             max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE,
             required=False,
             help_text=_('If you provide a logo image, we will by default not show your organization name '
    @@ -430,7 +430,7 @@ class OrganizerSettingsForm(SettingsForm):
         )
         favicon = ExtFileField(
             label=_('Favicon'),
    -        ext_whitelist=(".ico", ".png", ".jpg", ".gif", ".jpeg"),
    +        ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_FAVICON,
             required=False,
             max_size=settings.FILE_UPLOAD_MAX_SIZE_FAVICON,
             help_text=_('If you provide a favicon, we will show it instead of the default pretix icon. '
    
  • src/pretix/helpers/images.py+2 1 modified
    @@ -22,6 +22,7 @@
     import logging
     from io import BytesIO
     
    +from django.conf import settings
     from django.core.exceptions import ValidationError
     from django.utils.translation import gettext_lazy as _
     from PIL.Image import MAX_IMAGE_PIXELS, DecompressionBombError
    @@ -51,7 +52,7 @@ def validate_uploaded_file_for_valid_image(f):
     
         try:
             try:
    -            image = Image.open(file)
    +            image = Image.open(file, formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
                 # verify() must be called immediately after the constructor.
                 image.verify()
             except DecompressionBombError:
    
  • src/pretix/helpers/monkeypatching.py+16 0 modified
    @@ -21,6 +21,8 @@
     #
     from datetime import datetime
     
    +from PIL import Image
    +
     
     def monkeypatch_vobject_performance():
         """
    @@ -52,5 +54,19 @@ def new_tzinfo_eq(tzinfo1, tzinfo2, *args, **kwargs):
         icalendar.tzinfo_eq = new_tzinfo_eq
     
     
    +def monkeypatch_pillow_safer():
    +    """
    +    Pillow supports many file formats, among them EPS. For EPS, Pillow loads GhostScript whenever GhostScript
    +    is installed (cannot officially be disabled). However, GhostScript is known for regular security vulnerabilities.
    +    We have no use of reading EPS files and usually prevent this by using `Image.open(…, formats=[…])` to disable EPS
    +    support explicitly. However, we are worried about our dependencies like reportlab using `Image.open` without the
    +    `formats=` parameter. Therefore, as a defense in depth approach, we monkeypatch EPS support away by modifying the
    +    internal image format registry of Pillow.
    +    """
    +    if "EPS" in Image.ID:
    +        Image.ID.remove("EPS")
    +
    +
     def monkeypatch_all_at_ready():
         monkeypatch_vobject_performance()
    +    monkeypatch_pillow_safer()
    
  • src/pretix/helpers/reportlab.py+6 2 modified
    @@ -20,8 +20,9 @@
     # <https://www.gnu.org/licenses/>.
     #
     from arabic_reshaper import ArabicReshaper
    +from django.conf import settings
     from django.utils.functional import SimpleLazyObject
    -from PIL.Image import Resampling
    +from PIL import Image
     from reportlab.lib.utils import ImageReader
     
     
    @@ -33,7 +34,7 @@ def resize(self, width, height, dpi):
                 height = width * self._image.size[1] / self._image.size[0]
             self._image.thumbnail(
                 size=(int(width * dpi / 72), int(height * dpi / 72)),
    -            resample=Resampling.BICUBIC
    +            resample=Image.Resampling.BICUBIC
             )
             self._data = None
             return width, height
    @@ -44,6 +45,9 @@ def _jpeg_fh(self):
             # (smaller) size of the modified image.
             return None
     
    +    def _read_image(self, fp):
    +        return Image.open(fp, formats=settings.PILLOW_FORMATS_IMAGE)
    +
     
     reshaper = SimpleLazyObject(lambda: ArabicReshaper(configuration={
         'delete_harakat': True,
    
  • src/pretix/helpers/thumb.py+2 1 modified
    @@ -23,6 +23,7 @@
     import math
     from io import BytesIO
     
    +from django.conf import settings
     from django.core.files.base import ContentFile
     from django.core.files.storage import default_storage
     from PIL import Image, ImageOps, ImageSequence
    @@ -165,7 +166,7 @@ def resize_image(image, size):
     
     def create_thumbnail(sourcename, size):
         source = default_storage.open(sourcename)
    -    image = Image.open(BytesIO(source.read()))
    +    image = Image.open(BytesIO(source.read()), formats=settings.PILLOW_FORMATS_QUESTIONS_IMAGE)
         try:
             image.load()
         except:
    
  • src/pretix/plugins/sendmail/forms.py+1 5 modified
    @@ -76,11 +76,7 @@ class BaseMailForm(FormPlaceholderMixin, forms.Form):
         attachment = CachedFileField(
             label=_("Attachment"),
             required=False,
    -        ext_whitelist=(
    -            ".png", ".jpg", ".gif", ".jpeg", ".pdf", ".txt", ".docx", ".gif", ".svg",
    -            ".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages",
    -            ".bmp", ".tif", ".tiff"
    -        ),
    +        ext_whitelist=settings.FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT,
             help_text=_('Sending an attachment increases the chance of your email not arriving or being sorted into spam folders. We recommend only using PDFs '
                         'of no more than 2 MB in size.'),
             max_size=settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT
    
  • src/pretix/settings.py+18 0 modified
    @@ -733,4 +733,22 @@ def traces_sampler(sampling_context):
     FILE_UPLOAD_MAX_SIZE_EMAIL_AUTO_ATTACHMENT = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_email_auto_attachment", fallback=1)
     FILE_UPLOAD_MAX_SIZE_OTHER = 1024 * 1024 * config.getint("pretix_file_upload", "max_size_other", fallback=10)
     
    +# Allowed file extensions for various places plus matching Pillow formats.
    +# Never allow EPS, it is full of dangerous bugs.
    +FILE_UPLOAD_EXTENSIONS_IMAGE = (".png", ".jpg", ".gif", ".jpeg")
    +PILLOW_FORMATS_IMAGE = ('PNG', 'GIF', 'JPEG')
    +
    +FILE_UPLOAD_EXTENSIONS_FAVICON = (".ico", ".png", "jpg", ".gif", ".jpeg")
    +
    +FILE_UPLOAD_EXTENSIONS_QUESTION_IMAGE = (".png", "jpg", ".gif", ".jpeg", ".bmp", ".tif", ".tiff", ".jfif")
    +PILLOW_FORMATS_QUESTIONS_IMAGE = ('PNG', 'GIF', 'JPEG', 'BMP', 'TIFF')
    +
    +FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT = (
    +    ".png", ".jpg", ".gif", ".jpeg", ".pdf", ".txt", ".docx", ".gif", ".svg",
    +    ".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages",
    +    ".bmp", ".tif", ".tiff"
    +)
    +FILE_UPLOAD_EXTENSIONS_OTHER = FILE_UPLOAD_EXTENSIONS_EMAIL_ATTACHMENT
    +
    +
     DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'  # sadly. we would prefer BigInt, and should use it for all new models but the migration will be hard
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.