Afbeeldingen uploaden is niet simpelweg “een bestand ontvangen”; het is het doorgeven van externe input aan een decoder (parser). De drie Pillow-methoden gaan dus niet alleen over hun functionaliteit, maar vooral over wanneer ze worden aangeroepen (en zo het aanvalsoppervlak open te stellen).


open() is geen “pixel-ophaal-functie”

Image.open() werkt lazy. Het opent het bestand, identificeert het type en leest de pixeldata mogelijk nog niet. Het bestandshandle kan open blijven.

In beveiligings- en operationele omgevingen is het gebruik van open() eenvoudig:

  • Gebruik open() om het formaat, de breedte/hoogte en andere lichte informatie te verkrijgen.
  • Blokkeer via beleid: toegestane formaten, maximale resolutie/aantal pixels, uploadlimiet.
  • Ga vervolgens naar de volgende stap (verificatie/decodering).

Kortom, open() is een tool om beslissingsinformatie vóór decodering te extraheren.


Wat garandeert verify() en wat niet?

verify() probeert te bepalen of het bestand corrupt is, maar decodeert de daadwerkelijke afbeelding niet. Bij een fout wordt een uitzondering gegooid; om het bestand daarna nog te gebruiken, moet je het opnieuw openen.

Conclusies vanuit een beveiligingsperspectief:

  • Voordeel: Je kunt snel corrupte bestanden uitsluiten zonder zware decodering.
  • Beperking: Een bestand dat verify() doorstaat is niet automatisch veilig; problemen kunnen pas bij load() zichtbaar worden, omdat de volledige decodering nog niet is uitgevoerd.

load() is gevaarlijk als het onzorgvuldig wordt aangeroepen

load() voert de daadwerkelijke decodering (inclusief decompressie) uit en laadt de pixels in het geheugen. Dit is een directe DoS-aanvalsvector: een klein bestand kan na decompressie enorm groot worden.

Pillow waarschuwt voor een decompressiebom en heeft standaardlimieten (bijv. 128 Mpx). Django gebruikt om dezelfde reden verify() in plaats van load() bij het valideren van afbeelding-uploads. In de broncode staat een commentaar: “load() laadt het volledige beeld in het geheugen en vormt een DoS-vector”.


Bij gebruik van Django/DRF: ImageField en verify() kunnen dubbel zijn

Django’s ImageField voert intern Image.open() + verify() uit. DRF’s serializers.ImageField roept dezelfde Django-validatie aan.

Daarom:

  • Als je al serializers.ImageField gebruikt, is het meestal onnodig om in validate() nogmaals verify() aan te roepen.
  • Wil je extra beveiligings- of bedrijfsvalidaties, dan kun je beter een FileField gebruiken en je eigen validatie-pipeline opzetten.

Hoe garandeer je “veiligheid” voor geüploade bestanden?

Diagram van veilige verwerking van geüploade bestanden

De meest realistische aanpak is:

  1. Gebruik open() om lichte informatie te lezen en een eerste blokkering te doen.
  2. Gebruik verify() om duidelijk corrupte bestanden te verwijderen.
  3. Decodeer in een gecontroleerde omgeving en normaliseer naar standaard pixelformaten (RGB/RGBA).
  4. Herencodeer het resultaat in een door de server gekozen formaat.
  5. Sla en serveer alleen het hergecodeerde bestand.

Deze strategie geeft de server controle over de uiteindelijke output, waardoor onnodige metadata en vreemde structuren worden verwijderd. Let op: herencoding vereist nog steeds load(), dus stel vooraf geheugen- en pixellimieten in en voer het in een geïsoleerde worker of proces uit.


Samenvatting

  • open(): Identificatie + lichte informatie (pixels nog niet geladen).
  • verify(): Eerste filter op corruptie (zonder decodering, opnieuw openen nodig voor gebruik).
  • load(): Start van decodering/geheugengebruik (niet overmatig gebruiken in validatie).
  • Praktische oplossing: Vertrouw alleen op hergecodeerde bestanden (met limieten/isolatie).

Gerelateerde artikelen: