11 points par nuremberg 15 일 전 | Aucun commentaire pour le moment. | Partager sur WhatsApp

Résumé général en une ligne

Vulnérabilité de pré-authentification par épuisement CPU dans le MultiPartParser de Django, qui se produit lorsque le corps d’une partie avec Content-Transfer-Encoding: base64 est principalement composé d’espaces, provoquant avec une seule requête d’environ 2,5 Mo un temps de traitement plus de 2 100 fois supérieur à la normale (CVE-2026-33033)

Résumé

  • Peut être déclenchée sans authentification, même sur un serveur en configuration par défaut
    • Comme le middleware CSRF accède à request.POST avant l’entrée dans la vue, il exécute automatiquement MultiPartParser, si bien que même un endpoint authentifié consomme déjà plusieurs secondes dès l’étape de validation CSRF
  • Une seule requête de 20 Mo monopolise un worker unique pendant environ 1 minute
    • Avec une configuration gunicorn classique opérant avec 4 à 16 workers, quelques dizaines de requêtes simultanées suffisent à paralyser de fait le serveur
  • Django traite les requêtes multipart/form-data via MultiPartParser, et comme le middleware CSRF accède à request.POST avant l’entrée dans la vue, ce parseur s’exécute toujours même sans authentification
  • Le cœur de la vulnérabilité repose sur une structure où trois couches se multiplient
    • (Couche 1) boucle while d’alignement base64 : lorsque les espaces sont supprimés du chunk, l’état remaining != 0 persiste, ce qui entraîne des appels répétés à field_stream.read(1) sur tout le reste du flux
    • (Couche 2) coût caché en O(C) de LazyStream.read(1) : à chaque appel de read(1), un buffer interne d’environ 64 Ko est extrait en entier, puis 65 535 octets sont réinjectés via unget(), et ce schéma se répète
    • (Couche 3) concaténation de bytes en O(C) dans unget() : un nouvel objet bytes + self._leftover est créé à chaque fois
  • Une seule requête de 2,5 Mo provoque en interne environ 86 Go de copies mémoire, et monopolise complètement un worker pendant environ 5,3 secondes sur un M2. Avec 20 Mo, cela prend environ 1 minute
  • Un code de vérification de cohérence (_update_unget_history) existait déjà dans unget(), mais cette attaque suit un motif monotone décroissant où la taille de unget() baisse de 1 à chaque appel, ce qui ne satisfait jamais la condition de détection (number_equal > 40)
  • Le point clé du correctif de l’équipe Django est de remplacer read(4 - remaining) par read(self._chunk_size), afin de lire 64 Ko d’un coup au lieu de 1 à 3 octets à la fois. Le nombre d’appels à read passe ainsi de 2,5 millions à environ 40
  • La valeur par défaut de client_max_body_size dans Nginx est de 1 Mo, mais elle est souvent assouplie pour les endpoints d’upload de fichiers, et la valeur par défaut de LimitRequestBody dans Apache httpd est de 1 Go ; la protection par proxy seul ne garantit donc pas la défense
  • La vulnérabilité a été découverte avec l’aide de Claude Code + Codex, et le fait qu’un DoS pré-authentification soit resté présent dans un framework peaufiné depuis près de 20 ans est particulièrement marquant

Aucun commentaire pour le moment.

Aucun commentaire pour le moment.