Mit nur 20 MB an HTTP-Paketen lässt sich ein Django-Server für 1 Minute lahmlegen – veröffentlichte Schwachstelle (CVE-2026-33033)
(new-blog.ch4n3.kr)Gesamte Ein-Satz-Zusammenfassung
Eine Pre-Auth-CPU-Exhaustion-Schwachstelle im MultiPartParser von Django tritt auf, wenn der Part-Body mit Content-Transfer-Encoding: base64 überwiegend aus Leerraum besteht; eine einzelne Anfrage von etwa 2,5 MB verursacht dabei eine mehr als 2.100-fach längere Verarbeitungszeit im Vergleich zum Normalfall (CVE-2026-33033).
Zusammenfassung
- Lässt sich ohne Authentifizierung auslösen, auch auf Servern mit Standardeinstellungen
- Da die CSRF-Middleware vor dem Eintritt in die View auf
request.POSTzugreift und dadurch denMultiPartParserautomatisch ausführt, dauert selbst bei authentifizierten Endpunkten bereits die CSRF-Prüfphase mehrere Sekunden
- Da die CSRF-Middleware vor dem Eintritt in die View auf
- Eine einzelne 20-MB-Anfrage belegt einen einzelnen Worker etwa 1 Minute lang
- Bei einer typischen
gunicorn-Konfiguration mit 4 bis 16 Workern kann der Server faktisch schon durch einige Dutzend gleichzeitige Anfragen lahmgelegt werden
- Bei einer typischen
- Django verarbeitet
multipart/form-data-Anfragen mit demMultiPartParser, und weil die CSRF-Middleware bereits vor dem Eintritt in die View aufrequest.POSTzugreift, wird dieser Parser auch ohne Authentifizierung immer ausgeführt - Der Kern der Schwachstelle ist eine Struktur, in der sich drei Layer multiplizieren
- (Layer 1) base64-Ausrichtungs-while-loop: Wenn nach dem Entfernen von Leerraum
remaining != 0bestehen bleibt, wirdfield_stream.read(1)anschließend wiederholt für den restlichen gesamten Stream aufgerufen - (Layer 2) Versteckte O(C)-Kosten von
LazyStream.read(1): Bei jedem einzelnen Aufruf vonread(1)wird intern ein Puffer von ~64 KB vollständig entnommen und anschließend 65.535 Byte mitunget()wieder zurückgeschoben; dieses Muster wiederholt sich fortlaufend - (Layer 3) O(C)-
bytes-Konkatenation inunget(): Durchbytes + self._leftoverwird jedes Mal ein neues Objekt erzeugt
- (Layer 1) base64-Ausrichtungs-while-loop: Wenn nach dem Entfernen von Leerraum
- Eine einzelne Anfrage von 2,5 MB löst intern etwa 86 GB an Speicherkopien aus und belegt auf einem M2 einen Worker für etwa 5,3 Sekunden vollständig. Bei 20 MB dauert es etwa 1 Minute
- Innerhalb von
unget()war bereits Sanity-Check-Code (_update_unget_history) vorhanden, aber dieser Angriff folgt einem monoton fallenden Muster, bei dem dieunget()-Größe pro Aufruf jeweils um 1 sinkt; dadurch wird die Erkennungsbedingung (number_equal > 40) nie erfüllt - Der Kern des Patches des Django-Teams ist die Änderung von
read(4 - remaining)zuread(self._chunk_size), sodass statt 1 bis 3 Byte auf einmal nun jeweils 64 KB gelesen werden. Dadurch sinkt die Zahl derread-Aufrufe von 2,5 Millionen auf etwa 40 - Der Standardwert von Nginx
client_max_body_sizeliegt zwar bei 1 MB, wird aber bei Datei-Upload-Endpunkten häufig gelockert; außerdem beträgt der Standardwert von Apache httpdLimitRequestBody1 GB, sodass ein Proxy allein keinen garantierten Schutz bietet - Die Schwachstelle wurde mit Claude Code + Codex entdeckt, und bemerkenswert ist, dass in einem über fast 20 Jahre hinweg gereiften Framework noch immer ein Pre-Auth-DoS vorhanden war
4 Kommentare
Los geht’sss
Hat das jemand selbst ausprobiert?
Ein zu Demo-Zwecken erstellter PoC wurde auf GitHub hochgeladen.
https://github.com/ch4n3-yoon/CVE-2026-33033-PoC
Gefällt mir.