11 puan yazan nuremberg 15 일 전 | 4 yorum | WhatsApp'ta paylaş

Genel tek cümlelik özet

Django’nun MultiPartParser bileşeninde, Content-Transfer-Encoding: base64 parça gövdesi büyük ölçüde boşluk karakterlerinden oluştuğunda ortaya çıkan bir kimlik doğrulama öncesi CPU tüketimi açığı; yaklaşık 2.5 MB’lık tek bir istekle normale kıyasla 2.100 kattan fazla işleme süresi yaratıyor (CVE-2026-33033)

Özet

  • Kimlik doğrulama olmadan, varsayılan ayarlı sunucularda bile tetiklenebiliyor
    • CSRF middleware’i view’a girmeden önce request.POST alanına eriştiği için MultiPartParser otomatik olarak çalışıyor; bu nedenle kimlik doğrulamalı endpoint’lerde bile CSRF doğrulama aşamasında zaten birkaç saniye harcanıyor
  • Tek bir 20 MB’lık istek, tek bir worker’ı yaklaşık 1 dakika boyunca meşgul edebiliyor
    • 4–16 worker ile çalışan tipik bir gunicorn kurulumunda, eşzamanlı yalnızca birkaç düzine istek bile sunucuyu fiilen felç etmeye yetiyor
  • Django, multipart/form-data isteklerini MultiPartParser ile işler ve CSRF middleware’i view’a girmeden önce request.POST alanına eriştiği için bu parser kimlik doğrulama olmadan da her zaman çalıştırılır
  • Açığın özü, üç katmanın çarpan etkisi yaratmasında yatıyor
    • (Katman 1) base64 hizalama while-loop’u: chunk’tan boşluklar kaldırıldığında remaining != 0 durumu korunuyor ve sonrasında field_stream.read(1) tüm akış boyunca tekrar tekrar çağrılıyor
    • (Katman 2) LazyStream.read(1) içindeki gizli O(C) maliyet: read(1) bir kez çağrıldığında içeride yaklaşık 64 KB’lık tamponun tamamı çekiliyor, ardından 65.535 bayt unget() ile geri itiliyor; bu desen sürekli tekrarlanıyor
    • (Katman 3) unget() içindeki O(C) bytes birleştirmesi: her seferinde bytes + self._leftover şeklinde yeni bir nesne oluşturuluyor
  • Tek bir 2.5 MB’lık istek içeride yaklaşık 86 GB bellek kopyalamasına yol açıyor ve M2 ölçütünde tek bir worker’ı yaklaşık 5,3 saniye boyunca tamamen meşgul ediyor. 20 MB’ta bu süre yaklaşık 1 dakikaya çıkıyor
  • unget() içinde zaten bir sanity check kodu (_update_unget_history) vardı; ancak bu saldırıda unget() boyutu her çağrıda 1 azalan tekdüze bir desen izlediği için algılama koşulu olan number_equal > 40 hiçbir zaman sağlanmıyor
  • Django ekibinin yamasındaki temel değişiklik read(4 - remaining)read(self._chunk_size) oldu; yani her seferinde 1–3 bayt okumak yerine bir kerede 64 KB okunuyor. Böylece read çağrıları 2,5 milyon kereden yaklaşık 40’a düşüyor
  • Nginx’in client_max_body_size varsayılan değeri 1 MB olsa da dosya yükleme endpoint’lerinde bunun gevşetilmesi sık görülüyor; Apache httpd’de LimitRequestBody varsayılanı 1 GB olduğu için yalnızca proxy ile savunma garanti edilmiyor
  • Açık, Claude Code + Codex kullanılarak bulundu; yaklaşık 20 yıldır olgunlaşan bir framework’te kimlik doğrulama öncesi DoS açığının kalmış olması dikkat çekici

4 yorum

 
kalista22 14 일 전

Hadiii

 
tangokorea 15 일 전

Bunu bizzat deneyen var mı?

 
nuremberg 15 일 전

Demo amacıyla hazırlanmış PoC GitHub'a yüklenmiş durumda.

https://github.com/ch4n3-yoon/CVE-2026-33033-PoC

 
tangokorea 15 일 전

Harika.