CVE-2025-66019: pypdf Kütüphanesinde LZW Decompression DoS Zafiyeti
PDF dosyaları, içeriklerini sıkıştırmak için çeşitli algoritmalar kullanır. Bu sıkıştırma, dosya boyutunu küçültür ancak güvenlik açısından bazı riskler de taşır. Güvenlik araştırması yaparken, pypdf kütüphanesinin LZW (Lempel-Ziv-Welch) decompression implementasyonunda bir DoS (Denial of Service) zafiyeti buldum. Bu yazıda, önce LZW algoritmasının nasıl çalıştığını ve neden bu zafiyete yol açtığını anlatacağım, sonra da ZLIB gibi diğer algoritmaların neden bu sorundan etkilenmediğini açıklayacağım.
LZW Decompression Algoritması Nasıl Çalışır?
LZW (Lempel-Ziv-Welch), 1984’te geliştirilmiş bir sıkıştırma algoritmasıdır. PDF’lerde yaygın olarak kullanılır çünkü tekrarlayan pattern’leri çok etkili bir şekilde sıkıştırabilir.
Temel Prensip: Dictionary-Based Compression
LZW, temel olarak bir dictionary (sözlük) kullanarak çalışır. Algoritma, daha önce gördüğü pattern’leri dictionary’ye ekler ve tekrar gördüğünde bu pattern’leri kod numaralarıyla temsil eder.
Basit bir örnek:
Diyelim ki şu metni sıkıştırmak istiyoruz: "ABABABAB"
İnteraktif LZW Compression Demo
Decompression sırasında, bu kodları geri çözerek orijinal metni elde ederiz.
LZW Decompression Süreci
LZW decompression, sıkıştırılmış veriyi okuyarak dictionary’yi yeniden oluşturur:
1. Dictionary'yi başlat (ilk 256 karakter için standart ASCII)
2. İlk kodu oku ve çıktıya yaz
3. Her yeni kod için:
a. Kodu dictionary'de ara
b. Eğer bulunursa, çıktıya yaz
c. Yeni pattern oluştur (önceki + yeni karakter)
d. Yeni pattern'i dictionary'ye ekle
Görsel örnek:
İnteraktif LZW Decompression Demo
LZW’nin Güçlü Yönü: Yüksek Sıkıştırma Oranları
LZW, tekrarlayan pattern’lerle çalışırken çok yüksek sıkıştırma oranlarına ulaşabilir. Örneğin:
Orijinal veri: "AAAAA...AAAAA" (1000 karakter)
LZW sıkıştırma: [65, 256, 257, 258, ...] (~50 kod)
Sıkıştırma oranı: 20:1
Bu özellik, LZW’yi PDF’ler için ideal kılar çünkü PDF’lerde genellikle tekrarlayan pattern’ler (boşluklar, aynı renkler, vb.) bulunur.
Neden LZW Decompression DoS Zafiyetine Yol Açıyor?
LZW’nin bu yüksek sıkıştırma yeteneği, aynı zamanda bir güvenlik riski de yaratır. İşte neden:
1. Dictionary Büyümesi ve Bellek Kullanımı
LZW decompression sırasında, dictionary sürekli büyür. Her yeni pattern dictionary’ye eklenir ve bu pattern’ler bellekte tutulur.
Küçük sıkıştırılmış veri: 5 MB
Dictionary boyutu: Büyüyebilir (teorik olarak sınırsız)
Decompress edilmiş veri: 950 MB'a kadar çıkabilir
Sorun: pypdf, tüm decompress edilmiş veriyi bellekte tutar. Küçük bir PDF dosyası, decompression sonrası çok büyük bir bellek alanı kaplayabilir.
2. Tekrarlayan Pattern’lerin Exploit Edilmesi
Bir saldırgan, özellikle tekrarlayan pattern’ler içeren veri oluşturarak LZW’nin güçlü yönünü zafiyete çevirebilir:
Saldırgan verisi:
"ABCABCABCABC..." (1 milyon kez tekrar)
LZW sıkıştırma:
1. "ABC" → dictionary[256]
2. "BCA" → dictionary[257]
3. "CAB" → dictionary[258]
4. "ABC" → zaten var, kod 256 kullan
5. ... (çok yüksek sıkıştırma oranı)
Sonuç:
- Sıkıştırılmış: ~50 KB
- Decompress edilmiş: 3 MB
- Sıkıştırma oranı: 60:1
Daha büyük pattern’lerle bu oran daha da artabilir.
3. pypdf’in Mevcut Implementasyonu
pypdf’in LZW decompression implementasyonu şöyle çalışıyor:
# pypdf/_codecs/_codecs.py
class LzwCodec(Codec):
def decode(self, data: bytes) -> bytes:
output_stream = io.BytesIO()
output_length = 0
# Dictionary oluştur
dictionary = {i: bytes([i]) for i in range(256)}
# Decompression döngüsü
while True:
code = read_next_code()
if code in dictionary:
decoded = dictionary[code]
else:
# Special case: code not in dictionary yet
decoded = previous + previous[0:1]
output_stream.write(decoded)
output_length += len(decoded)
# Limit kontrolü
if output_length > self.max_output_length:
raise LimitReachedError(...)
# Yeni pattern dictionary'ye ekle
new_pattern = previous + decoded[0:1]
dictionary[next_code] = new_pattern
return output_stream.getvalue() # Tüm veri bellekte!
Kritik sorun: output_stream.getvalue() çağrısı, tüm decompress edilmiş veriyi bellekte tutar. Bu, büyük dosyalar için ciddi bellek tüketimine neden olur.
4. Root Cause: Bellekte Tüm Veriyi Tutma
LZW decompression’ın doğası gereği, tüm çıktıyı bellekte tutmak gerekli değildir. Ancak pypdf’in mevcut implementasyonu, tüm veriyi BytesIO buffer’ında tutuyor ve sonunda getvalue() ile tek seferde döndürüyor.
Neden bu bir sorun?
- Streaming yaklaşımı mümkün: LZW decompression, streaming olarak yapılabilir (chunk’lar halinde)
- Bellek verimliliği: Büyük dosyalar için streaming, bellek kullanımını önemli ölçüde azaltır
- DoS riski: Tüm veriyi bellekte tutmak, küçük PDF’lerin büyük bellek tüketimine yol açmasına izin verir
Neden ZLIB Bu Sorundan Etkilenmiyor?
ZLIB (ve DEFLATE), LZW’den farklı bir yaklaşım kullanır. Bu fark, ZLIB’yi bu tür DoS saldırılarına karşı daha dayanıklı kılar.
ZLIB/DEFLATE Algoritması
ZLIB, DEFLATE algoritmasını kullanır. DEFLATE, iki aşamalı bir sıkıştırma yöntemidir:
- LZ77 (Lempel-Ziv 77): Sliding window kullanarak tekrarlayan pattern’leri bulur
- Huffman Coding: Bulunan pattern’leri daha verimli kodlar
Temel fark: ZLIB, sliding window kullanır, LZW ise growing dictionary kullanır.
Sliding Window vs Growing Dictionary
Dictionary vs Window Karşılaştırması
LZW (Growing Dictionary)
ZLIB (Sliding Window)
ZLIB’nin DoS’a Karşı Dayanıklılığı
1. Sınırlı Window Boyutu:
ZLIB’nin sliding window’u sabit boyutludur (genellikle 32 KB). Bu, dictionary’nin sınırsız büyümesini önler.
LZW: Dictionary → sınırsız büyüyebilir
ZLIB: Window → sabit 32 KB
2. Daha Düşük Sıkıştırma Oranları:
ZLIB, LZW kadar yüksek sıkıştırma oranlarına ulaşamaz (özellikle tekrarlayan pattern’ler için):
Tekrarlayan pattern: "ABCABCABC..."
LZW sıkıştırma oranı: 20-50:1
ZLIB sıkıştırma oranı: 5-10:1
Bu, saldırganların küçük dosyalarla büyük bellek tüketimi yaratmasını zorlaştırır.
3. pypdf’in ZLIB Limiti:
pypdf, ZLIB için daha düşük bir limit kullanıyor:
# pypdf/filters.py
ZLIB_MAX_OUTPUT_LENGTH = 75_000_000 # 75 MB
LZW_MAX_OUTPUT_LENGTH = 1_000_000_000 # 1 GB (13.3x daha yüksek!)
Bu tutarsızlık, LZW’yi daha riskli hale getiriyor.
Karşılaştırma Tablosu
| Özellik | LZW | ZLIB/DEFLATE |
|---|---|---|
| Dictionary/Window | Growing dictionary | Fixed sliding window |
| Maksimum boyut | Teorik olarak sınırsız | Sabit (32 KB) |
| Sıkıştırma oranı (tekrarlayan pattern) | 20-50:1 | 5-10:1 |
| Bellek kullanımı (decompression) | Yüksek (tüm veri) | Daha düşük (streaming mümkün) |
| DoS riski | Yüksek | Daha düşük |
| pypdf limiti | 1 GB | 75 MB |
CVE-2025-66019: Zafiyet Detayları
Zafiyet Özeti
CVE-2025-66019, pypdf kütüphanesinin LZW decompression implementasyonunda bulunan bir DoS zafiyetidir. Kütüphane, önceki bir güvenlik düzeltmesi (CVE-2025-62708) ile bir limit mekanizması eklemişti, ancak 1 GB limit değeri pratik güvenlik açısından hala yetersiz.
Temel Bulgular:
- ✅ Limit mekanizması mevcut (6.1.3 versiyonunda eklendi)
- ⚠️ Limit değeri (1 GB) pratik güvenlik için çok yüksek
- ⚠️ ZLIB limiti ile tutarsızlık (75 MB vs 1 GB)
- ✅ Proof of Concept ile exploit edilebilirliği kanıtlandı
Etkilenen Bileşenler
- Dosya:
pypdf/_codecs/_codecs.py(LzwCodec sınıfı) - Dosya:
pypdf/filters.py(LZWDecode sınıfı) - Mevcut Limit:
LZW_MAX_OUTPUT_LENGTH = 1_000_000_000(1 GB)
Patched Versions
Bu zafiyet pypdf >= 6.4.0 versiyonunda düzeltildi. Etkilenen kullanıcıların kütüphaneyi güncellemeleri önerilir.
Pratik Saldırı Senaryosu
Saldırgan şunları yapabilir:
- 950 MB decompress edilmiş veri oluşturur (tekrarlayan pattern’ler)
- LZW sıkıştırması ile ~4-20 MB PDF dosyası oluşturur
- Sunucuya yükler
Sunucu (pypdf):
- Küçük PDF’i okur (20 MB)
- LZW stream’ini decompress eder
- 950 MB bellek tahsis eder
- CPU-yoğun işleme (1-3 dakika)
- Bellek tükenmesi → DoS
Proof of Concept Sonuçları
Test PDF (100 MB decompress edilmiş):
- PDF dosya boyutu: ~2-5 MB (sıkıştırılmış)
- Decompress edilmiş boyut: ~100 MB
- İşleme süresi: 5-15 saniye
- Bellek kullanımı: +100-150 MB
Tam Etki PDF (950 MB decompress edilmiş):
- PDF dosya boyutu: ~4-20 MB (sıkıştırılmış)
- Decompress edilmiş boyut: ~950 MB
- İşleme süresi: 1-3 dakika
- Bellek kullanımı: +950-1100 MB
Sıkıştırma Oranı Gösterimi
Demo sonuçları:
- Basit tekrarlayan veri: 18.87:1 sıkıştırma
- Pattern tabanlı veri: 19.74:1 sıkıştırma
- Büyük sıkıştırılabilir veri: 219.28:1 sıkıştırma
- Rastgele veri: 0.74:1 (LZW kötü performans gösterir)
Etki Değerlendirmesi
Etkilenen Ortamlar
Serverless Fonksiyonlar (AWS Lambda, Cloud Functions):
- Bellek limitleri: 128 MB - 10 GB
- Etki: Bellek tükenmesi, çökmeler, maliyet artışı
API Endpoint’leri (PDF işleme servisleri):
- Eşzamanlı istekler
- Etki: Kaynak tükenmesi, servis bozulması
Embedded Sistemler:
- Sınırlı RAM (256 MB - 1 GB)
- Etki: Sistem çökmeleri, denial of service
Web Uygulamaları:
- PDF yükleme/işleme özellikleri
- Etki: Sunucu yavaşlaması, bellek tükenmesi
Sonuç
CVE-2025-66019, pypdf kütüphanesinde bulunan bir DoS zafiyetidir. Zafiyetin kök nedeni, LZW algoritmasının yüksek sıkıştırma yeteneği ve pypdf’in tüm decompress edilmiş veriyi bellekte tutmasıdır. ZLIB gibi algoritmalar, sliding window yaklaşımı sayesinde bu sorundan daha az etkilenir. Bu zafiyet pypdf >= 6.4.0 versiyonunda düzeltildi.
İlgili İçerikler:
- exifLooter: Fotoğraflardan Gizli Konum Bilgilerini Çıkarma
- PassDetective: Shell Geçmişinizdeki Şifre ve Secret’ları Tespit Etme
Kaynaklar: