Jul 21, 2026·5 min read·65 visits
Unbounded zlib decompression in Pillow's PDF parser allows remote attackers to cause system Out-of-Memory crashes using crafted PDF files.
CVE-2026-59200 is a high-severity uncontrolled resource consumption vulnerability in the Pillow Python Imaging Library. The flaw resides in the PDF stream decoder, allowing remote, unauthenticated attackers to trigger host out-of-memory crashes by submitting malicious PDF decompression bombs.
Pillow is a widely adopted Python Imaging Library that handles graphic metadata parsing and image manipulation. Within its PDF handling routines, specifically inside the PDF stream decoder implemented in PdfParser.PdfStream.decode() in PIL/PdfParser.py, the library provides capability for decompressing binary vector data blocks.
The vulnerability is classified as an uncontrolled resource consumption flaw, mapping to CWE-400 and CWE-770, commonly referred to as a decompression bomb. Because decompression routines are processed synchronously upon parsing complex file formats, this flaw exposes a significant remote attack surface.
Any application utilizing Pillow to automatically process untrusted PDF uploads, generate thumbnails, or inspect image metadata is susceptible. The vulnerability does not require authentication or user interaction to trigger, making it an effective vector for targeting downstream processing daemons.
The underlying bug stems from an architectural misunderstanding of Python's standard zlib.decompress(data, bufsize) function. When the PDF stream parser encounters a /FlateDecode filter, it reads the /Length or /DL properties defined within the PDF dictionary wrapper to determine the expected decompressed stream length.
The developer assumed that specifying bufsize=int(expected_length) would act as a security limit, preventing Python's zlib wrapper from returning output that exceeds the specified length. In reality, Python's implementation uses the bufsize parameter solely as an initial memory buffer allocation hint to optimize execution, not as a maximum boundary limit.
When a zlib stream is parsed, if the actual uncompressed payload exceeds this initial size hint, the underlying C-native implementation automatically grows the allocation buffer dynamically. An attacker can construct a payload consisting of highly repetitive bytes (e.g., recursive null blocks) that compress highly efficiently but expand into gigabytes. This leads to unbounded memory allocation on the heap, triggering immediate host resource exhaustion and operating system kernel-level OOM (Out-Of-Memory) process termination.
The vulnerable code path inside PIL/PdfParser.py relied on direct execution of zlib.decompress using the untrusted length header:
# Vulnerable Implementation (< 12.3.0)
def decode(self) -> bytes:
try:
filter = self.dictionary[b"Filter"]
except KeyError:
return self.buf
if filter == b"FlateDecode":
try:
expected_length = self.dictionary[b"DL"]
except KeyError:
expected_length = self.dictionary[b"Length"]
# The bufsize parameter only acts as an allocation hint
return zlib.decompress(self.buf, bufsize=int(expected_length))The patched version replaces this logical block, introducing a secure streaming decompressor instantiation via zlib.decompressobj(). The patch implements validation using ImageFile.SAFEBLOCK as a hard limit:
# Patched Implementation (>= 12.3.0)
def decode(self, max_length: int = ImageFile.SAFEBLOCK) -> bytes:
try:
filter = self.dictionary[b"Filter"]
except KeyError:
return self.buf
if filter == b"FlateDecode":
# Use secure streaming object decompressor
dobj = zlib.decompressobj()
plaintext = dobj.decompress(self.buf, max_length)
# If there are unconsumed input bytes remaining, size exceeded the threshold
if dobj.unconsumed_tail:
msg = "Decompressed data too large"
raise ValueError(msg)
return plaintextBy leveraging dobj.unconsumed_tail, the parser validates if there is remaining compressed content that was not inflated because the max_length restriction was hit. If present, the routine immediately aborts, raising a ValueError prior to the allocation of additional system memory.
To exploit this vulnerability, an attacker constructs a PDF document containing a compressed stream object. The stream's content represents a high-expansion compression bomb utilizing standard DEFLATE compression.
The attacker structures the stream object with a /Length tag matched to the small compressed size to evade simple perimeter length checks. Once submitted to the target application, the backend parses the file and initiates decompression via the vulnerable PdfStream.decode() method.
Because the Python process executes this decompression step synchronously inside a native C module, the application is unable to interdict or limit the allocation once execution has been handed off to the standard library. The process continues to claim resources until terminated by the operating system kernel.
The impact of CVE-2026-59200 is restricted to availability, yielding a High score under the CVSS metric. Execution of the exploit results in an immediate Out-of-Memory event on the target host processing the file.
In standard enterprise environments, this triggers the OS kernel OOM-killer to terminate the application's daemon worker process (e.g., Gunicorn worker, Celery runner, or uWSGI process). This leads to localized Denial of Service for active connections and processing queues.
While this vulnerability does not yield remote code execution or data exposure, it represents a highly stable vector for disrupting service availability. Because the attack requires no privileges, any public-facing upload portal utilizing vulnerable parser modules is exposed to disruption.
The primary remediation path is upgrading the local Pillow dependency to version 12.3.0 or higher, which contains the official patch. This replaces the insecure decompress path with the streaming verification logic.
Where upgrading is not immediately feasible, you can apply workarounds at the application level. Ensure that applications restrict processed file types by explicitly defining safe formats within Image.open instead of utilizing default format detection.
# Safe application entry-point handling
SAFE_FORMATS = ["PNG", "JPEG", "WEBP"]
try:
im = Image.open(user_file, formats=SAFE_FORMATS)
im.verify()
except Exception as e:
raise ValueError("Unsupported format requested")Additionally, restrict operational resources at the operating system or containerization level. By establishing strict memory constraints on individual daemon processes, you can prevent a single payload execution from exhausting the entire virtual host resource pool.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Attribute | Detail |
|---|---|
| CWE ID | CWE-400 |
| Attack Vector | Network |
| CVSS | 7.5 |
| EPSS Score | 0.0035 |
| Impact | High (Denial of Service) |
| Exploit Status | Proof-of-Concept Available |
| KEV Status | Not Listed |
The software does not properly control the allocation of memory during decompression, enabling remote attackers to exhaust host RAM.
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.
Contao Open Source CMS versions 4.0.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 contain a Cross-Site Request Forgery (CSRF) vulnerability in backend parameter handling. The `RequestTokenListener` component validates anti-CSRF tokens solely for HTTP POST requests, while GET-based declarative guards run only when an `act` parameter is present in the query string. Consequently, custom backend actions dispatched via alternative parameters such as `key=` can execute without CSRF token verification when triggered by an authenticated user.
Contao CMS versions 4.1.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 fail to validate form submission tokens and enforce rate limiting when processing activation email resend requests via HTTP POST, enabling resource exhaustion and account state enumeration.
An information disclosure vulnerability in Contao CMS allows unauthenticated site visitors to view protected page titles, URLs, and text excerpts through search queries when protected page indexing is disabled after previously being enabled.
In Vikunja prior to version 2.6.0, relation creation via the CalDAV endpoint fails to invoke the TaskRelation.CanCreate authorization check. This missing access control allows an authenticated user to establish unauthorized relationships and perform write operations against any task, provided its unique identifier (UID) is known.
A cross-project information disclosure vulnerability in Vikunja allows authenticated users with read access to one project to view private task details from unauthorized projects via subtask expansion parameters.