11 Punkte von nuremberg 15 일 전 | 4 Kommentare | Auf WhatsApp teilen

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.POST zugreift und dadurch den MultiPartParser automatisch ausführt, dauert selbst bei authentifizierten Endpunkten bereits die CSRF-Prüfphase mehrere Sekunden
  • 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
  • Django verarbeitet multipart/form-data-Anfragen mit dem MultiPartParser, und weil die CSRF-Middleware bereits vor dem Eintritt in die View auf request.POST zugreift, 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 != 0 bestehen bleibt, wird field_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 von read(1) wird intern ein Puffer von ~64 KB vollständig entnommen und anschließend 65.535 Byte mit unget() wieder zurückgeschoben; dieses Muster wiederholt sich fortlaufend
    • (Layer 3) O(C)-bytes-Konkatenation in unget(): Durch bytes + self._leftover wird jedes Mal ein neues Objekt erzeugt
  • 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 die unget()-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) zu read(self._chunk_size), sodass statt 1 bis 3 Byte auf einmal nun jeweils 64 KB gelesen werden. Dadurch sinkt die Zahl der read-Aufrufe von 2,5 Millionen auf etwa 40
  • Der Standardwert von Nginx client_max_body_size liegt zwar bei 1 MB, wird aber bei Datei-Upload-Endpunkten häufig gelockert; außerdem beträgt der Standardwert von Apache httpd LimitRequestBody 1 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

 
kalista22 14 일 전

Los geht’sss

 
tangokorea 15 일 전

Hat das jemand selbst ausprobiert?

 
nuremberg 15 일 전

Ein zu Demo-Zwecken erstellter PoC wurde auf GitHub hochgeladen.

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

 
tangokorea 15 일 전

Gefällt mir.