
نشرة: CVE-2026-38361 ثغرات متعددة من نوع حجب الخدمة (DoS) (CWE-400/CWE-670) في dash-uploader (Python/PyPI)
ثغرات متعددة لحجب الخدمة (Denial of Service - DoS) دون مصادقة في fohrloop/dash-uploader (Python، PyPI)، بما في ذلك (على سبيل المثال لا الحصر) انهيار العملية بسبب نفاد الذاكرة (OOM)، واقتطاع الملفات إلى صفر بايت، واستنزاف القرص بشكل دائم، والالتفاف الكامل على حد max_file_size الموثق. توجد مسارات إضافية لإساءة استخدام الموارد عبر نفس مجموعة المعاملات غير المعقّمة.
تمت أرشفة المستودع في 2025-07-19 دون وجود مشرف نشط. جميع الإصدارات المنشورة (من 0.1.0 حتى 0.7.0a2) متأثرة وستظل كذلك. لا تزال الحزمة تحقق ما يقارب 28,000 تحميل شهريًا.
يجب على أي شخص يشغّل dash-uploader في بيئة الإنتاج تطبيق إجراء تخفيف بنفسه. الإصلاح الموصى به هو الانتقال إلى مكوّن dcc.Upload المدمج في Plotly Dash. راجع التخفيف للاطلاع على الخيارات الكاملة.
يستقبل معالج HTTP الخاص بـ dash-uploader طلبات POST غير مصادق عليها تحتوي على معاملات يتحكم فيها المهاجم وتتدفق إلى تخصيص الذاكرة وعمليات الملفات وإنشاء الدلائل دون أي فحوصات للحدود أو حدود لمعدل الطلبات أو آلية تنظيف. توجد أربع مشكلات مستقلة في نفس مسار الكود:
تم التحقق على نظام بسعة 7.7 جيجابايت: 5 طلبات POST متزامنة مع resumableTotalChunks=30000000 أدت إلى تفعيل مُنهي OOM في Linux خلال ثانيتين. يؤكد سجل النواة:
Out of memory: Killed process 24203 (python3) total-vm:8302276kB, anon-rss:7068012kB
يخصص كل طلب ما يقارب 2.9 جيجابايت عبر فهم القائمة (list comprehension) على range(1, resumableTotalChunks + 1). يتم إنهاء عملية الخادم ويصبح التطبيق غير متاح تمامًا حتى إعادة التشغيل اليدوية.
تم تقليص ملف يحتوي على 42 بايت من البيانات إلى 0 بايت عبر طلب POST واحد مع resumableTotalChunks=0. السبب الجذري هو أن دالة all() في Python تُرجع True للمتكرّرات الفارغة، مما يخدع معالج الرفع لاعتبار صفر شظايا عملية رفع مكتملة. يتم حذف الملف الموجود عبر os.unlink() واستبداله بملف فارغ.
تم إنشاء 10 دلائل مؤقتة يتيمة تحتوي على ملفات شظايا وبقيت مخزنة على القرص إلى أجل غير مسمى. بحث شامل في جميع ملفات المصدر عبر قاعدة الكود عن cleanup وttl وexpire وgarbage وpurge وcron وschedule وperiodic لم يُرجع أي نتائج. استدعاء التنظيف الوحيد (shutil.rmtree) يتم تنفيذه حصريًا على عمليات الرفع المكتملة. لا توجد أي آلية لاستعادة مساحة القرص من الجلسات غير المكتملة.
max_file_size (مُتحقَّق منه)قبل الخادم شظية بحجم 5 ميجابايت لملف يدّعي resumableTotalSize=999999999999 (~999 جيجابايت) مع استجابة HTTP 200. يتم تمرير معامل max_file_size فقط إلى مكوّن React JavaScript. لا يقوم الخادم أبدًا بفحص حجم الملف أو حجم الشظية أو Content-Length أو MAX_CONTENT_LENGTH الخاص بـ Flask. المطوّر الذي يضبط max_file_size=10 لا يحصل على أي حماية من جانب الخادم.
# dash_uploader/httprequesthandler.py
def _post(self):
resumableTotalChunks = request.form.get("resumableTotalChunks", type=int) # attacker-controlled, no bounds
...
chunk_paths = [
os.path.join(temp_dir, get_chunk_name(resumableFilename, x))
for x in range(1, resumableTotalChunks + 1) # unbounded; e.g. 30M -> ~2.9 GB -> OOM
]
upload_complete = all([os.path.exists(p) for p in chunk_paths]) # all([]) is True -> truncation when chunks=0
if upload_complete:
target_file_name = os.path.join(temp_root, resumableFilename)
if os.path.exists(target_file_name):
os.unlink(target_file_name) # existing file deleted
with open(target_file_name, "ab") as target_file:
for p in chunk_paths: # empty list -> empty file written
...
ينتج مسار الكود نفسه كلًا من انهيار OOM (قيمة resumableTotalChunks كبيرة) وأولية اقتطاع الملف (resumableTotalChunks=0).
يرسل المهاجم طلبات POST غير مصادق عليها إلى نقطة النهاية /API/resumable.
resumableTotalChunks=30000000 تخصص ~2.9 جيجابايت لكل طلب، مما يؤدي إلى تفعيل مُنهي OOM.resumableTotalChunks=0؛ تخدع all([])=True في Python الخادم لاستبدال الملف الهدف بمحتوى فارغ.لا تتطلب أي مصادقة أو صلاحيات.
resumableTotalChunks)os.makedirs() مع resumableIdentifier غير معقّمall() في Python القيمة True للمتكرّرات الفارغة عندما يكون resumableTotalChunks=0max_file_size يُطبَّق فقط في JavaScript من جانب العميل بينما لا يقوم المعالج من جانب الخادم بأي تحقق من الحجم ولا يضبط أبدًا MAX_CONTENT_LENGTH الخاص بـ Flaskdash_uploader/httprequesthandler.py (طريقة BaseHttpRequestHandler._post)dash_uploader/upload.py (دالة Upload، معامل max_file_size)dash_uploader/configure_upload.py (غياب MAX_CONTENT_LENGTH)الخيارات المتاحة للمستخدمين الذين لديهم نشر قائم حاليًا، حسب الأولوية:
dcc.Upload، مكوّن الرفع الرسمي المرفق مع Plotly Dash. لا يحتوي على معامل لعدد الشظايا، ولا حالة مؤقتة على القرص، ويلتزم بـ MAX_CONTENT_LENGTH الخاص بـ Flask. لا تنطبق أي من المشكلات الأربع هنا. الأنسب للملفات الصغيرة والمتوسطة. بالنسبة لعمليات الرفع الكبيرة جدًا، راجع البند 2.MAX_CONTENT_LENGTH)، وحدود على أي عدد شظايا يقدمه العميل، وقائمة مسموح بها لأسماء الملفات المقبولة.MAX_CONTENT_LENGTH الخاص بـ Flask على مستوى التطبيق (المكتبة لا تفعل ذلك)، وارفض المدخلات على طبقة التطبيق أو الوكيل العكسي عند تحقق أي مما يلي:
resumableTotalChunks <= 0resumableTotalChunks لحد معقول (مثل 10,000)resumableTotalSize لقيمة max_file_size التي ضبطها المطوّر0.6.1 (خط الإصدار المستقر). تمتد الإصدارات الأولية إلى 0.7.0a2.dash. الاعتمادية الاختيارية: pyyaml. الترخيص: MIT.Muhammad Fitri Bin Mohd Sultan
| معرّف CVE | CVE-2026-38361 (NVD) |
| الثغرة | استهلاك غير متحكم به للموارد (CWE-400)، تنفيذ تدفق تحكم غير صحيح دائمًا (CWE-670) |
| CVSS 3.1 | 7.5 / عالية (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) |
| المنتج | dash-uploader |
| الإصدارات المتأثرة | 0.1.0 حتى 0.7.0a2 (جميع الإصدارات الـ 18) |
| الإصدار المُصحَّح | لا يوجد (تمت أرشفة المشروع في 2025-07-19) |
| ناقل الهجوم | عن بُعد، دون مصادقة |
| المكتشف | Muhammad Fitri Bin Mohd Sultan |
| مُعيَّن من قبل | MITRE، 2026-05-07 |
| مرتبط | CVE-2026-38360 (اجتياز المسار في نفس المكتبة) |
| التاريخ | الحدث |
|---|
| 2026-03-19 | اكتشاف الثغرات أثناء بحث أمني على نشر إنتاجي. |
| 2026-03-22 | تقديم طلب CVE إلى MITRE. |
| 2026-05-07 | تم تعيين CVE-2026-38361 من قبل MITRE. |
| 2026-05-07 | تم نشر الاستشارة الأمنية العامة. |
| 2026-05-09 | تم نشر سجل CVE في قاعدة بيانات CVE الخاصة بـ MITRE وقاعدة NVD. |