Skip to content
KitploitKITPLOIT
FerramentasExploitsBlog
Log in
Enviar
FerramentasExploitsBlog
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.

FeedsContatoPrivacidade© 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
21há 7 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:

[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)

// 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.

// 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.

/* 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)
Baixar ferramenta