Une vulnérabilité permettant de rendre un serveur Django indisponible pendant 1 minute avec seulement 20 Mo de paquets HTTP a été divulguée (CVE-2026-33033)
(new-blog.ch4n3.kr)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.POSTavant l’entrée dans la vue, il exécute automatiquementMultiPartParser, si bien que même un endpoint authentifié consomme déjà plusieurs secondes dès l’étape de validation CSRF
- Comme le middleware CSRF accède à
- 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-dataviaMultiPartParser, et comme le middleware CSRF accède àrequest.POSTavant 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
whiled’alignement base64 : lorsque les espaces sont supprimés du chunk, l’étatremaining != 0persiste, 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 deread(1), un buffer interne d’environ 64 Ko est extrait en entier, puis 65 535 octets sont réinjectés viaunget(), et ce schéma se répète - (Couche 3) concaténation de
bytesen O(C) dansunget(): un nouvel objetbytes + self._leftoverest créé à chaque fois
- (Couche 1) boucle
- 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à dansunget(), mais cette attaque suit un motif monotone décroissant où la taille deunget()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)parread(self._chunk_size), afin de lire 64 Ko d’un coup au lieu de 1 à 3 octets à la fois. Le nombre d’appels àreadpasse ainsi de 2,5 millions à environ 40 - La valeur par défaut de
client_max_body_sizedans Nginx est de 1 Mo, mais elle est souvent assouplie pour les endpoints d’upload de fichiers, et la valeur par défaut deLimitRequestBodydans 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.