Yalnızca 20 MB’lık HTTP paketleriyle bir Django sunucusunu 1 dakika boyunca kilitleyebilen açık yayımlandı (CVE-2026-33033)
(new-blog.ch4n3.kr)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.POSTalanına eriştiği içinMultiPartParserotomatik olarak çalışıyor; bu nedenle kimlik doğrulamalı endpoint’lerde bile CSRF doğrulama aşamasında zaten birkaç saniye harcanıyor
- CSRF middleware’i view’a girmeden önce
- 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-dataistekleriniMultiPartParserile işler ve CSRF middleware’i view’a girmeden öncerequest.POSTalanı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 != 0durumu korunuyor ve sonrasındafield_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 baytunget()ile geri itiliyor; bu desen sürekli tekrarlanıyor - (Katman 3)
unget()içindeki O(C)bytesbirleştirmesi: her seferindebytes + self._leftoverşeklinde yeni bir nesne oluşturuluyor
- (Katman 1) base64 hizalama while-loop’u: chunk’tan boşluklar kaldırıldığında
- 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ıdaunget()boyutu her çağrıda 1 azalan tekdüze bir desen izlediği için algılama koşulu olannumber_equal > 40hiç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öylecereadçağrıları 2,5 milyon kereden yaklaşık 40’a düşüyor - Nginx’in
client_max_body_sizevarsayılan değeri 1 MB olsa da dosya yükleme endpoint’lerinde bunun gevşetilmesi sık görülüyor; Apache httpd’deLimitRequestBodyvarsayı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
Hadiii
Bunu bizzat deneyen var mı?
Demo amacıyla hazırlanmış PoC GitHub'a yüklenmiş durumda.
https://github.com/ch4n3-yoon/CVE-2026-33033-PoC
Harika.