
# مختبر Docker محلي يعيد إنتاج CVE-2026-39987، ثغرة تنفيذ التعليمات البرمجية عن بُعد (RCE) قبل المصادقة في WebSocket الطرفية الخاص بـ marimo. يقارن بين الإصدارات المعرضة للخطر والإصدارات المُصححة مع نصوص PoC الأقل ضررًا لأغراض البحث الأمني التعليمي.
مختبر Docker محلي فقط لإعادة إنتاج وفهم CVE-2026-39987 في marimo. يقارن هذا المشروع خدمة marimo الثغرة مع خدمة marimo المُصححة جنبًا إلى جنب، ثم يوضح الفرق باستخدام سكربت إثبات بأقل ضرر.
CVE-2026-39987 هي ثغرة حرجة لتنفيذ التعليمات البرمجية عن بُعد قبل المصادقة في marimo، وهو إطار عمل دفاتر ملاحظات Python تفاعلي.
السلوك الثغري موجود في نقطة نهاية WebSocket الطرفية:
/terminal/ws
في الإصدارات المتأثرة، يمكن الوصول إلى هذه النقطة دون مصادقة صالحة ويمكنها إنشاء جلسة طرفية تفاعلية. يمكن لمهاجم غير مصادق يتمكن من الوصول إلى خادم تحرير marimo الثغري تنفيذ أوامر بصلاحيات عملية marimo.
يعيد هذا المختبر إنتاج الثغرة في بيئة Docker محلية خاضعة للتحكم:
| الخدمة | الإصدار | الرابط | السلوك المتوقع |
|---|---|---|---|
vuln | marimo 0.20.4 | http://127.0.0.1:8081 | /terminal/ws يقبل اتصال WebSocket غير مصادق |
patched | marimo 0.23.0 | http://127.0.0.1:8082 | /terminal/ws يرفض اتصال WebSocket غير مصادق مع 403 Forbidden |
الهدف ليس توفير استغلال مُسلح. الهدف هو إظهار الفرق الأمني الملحوظ بين الإصدار الثغري والإصدار المُصحح باستخدام أدلة محلية قابلة لإعادة الإنتاج.
المكوّن المتأثر هو نقطة نهاية WebSocket الطرفية في marimo:
/terminal/ws
تُستخدم هذه النقطة بواسطة بيئة تحرير marimo لتوفير وظائف طرفية عبر جلسة WebSocket متصلة بالمتصفح.
كانت النقطة الثغرية تقبل اتصالات WebSocket دون فرض نفس فحوصات المصادقة المتوقعة لوظائف تحرير marimo المحمية.
الفرق المهم هو:
السلوك الثغري:
يمكن للعميل غير المصادق الاتصال بـ /terminal/ws
يتم إنشاء جلسة طرفية
يمكن إرسال الأوامر عبر WebSocket
السلوك المُصحح:
يتم رفض العميل غير المصادق
يفشل مصافحة WebSocket مع 403 Forbidden
لا يتم إنشاء جلسة طرفية
يضيف التصحيح التحقق من المصادقة إلى تدفق WebSocket الطرفي قبل السماح بإنشاء جلسة طرفية.
إذا تم تعريض خادم تحرير marimo الثغري لشبكة يمكن الوصول إليها، فقد يتمكن مهاجم غير مصادق من تنفيذ أوامر كمستخدم يقوم بتشغيل عملية marimo.
في هذا المختبر، يتم تشغيل عملية marimo عمدًا كمستخدم غير جذر منخفض الصلاحيات:
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)
هذا يحافظ على أمان العرض التوضيحي مع إثبات الثغرة.
صُمم هذا المختبر لإثبات ثلاثة أشياء:
/terminal/ws.403 Forbidden.يتجنب المختبر عمدًا الأوامر التدميرية، والاستمرارية، والصدور العكسي، وسرقة بيانات الاعتماد، أو أي هدف مواجه للإنترنت.
.
├── docker-compose.yml
├── vuln/
│ ├── Dockerfile
│ └── notebook.py
├── patched/
│ ├── Dockerfile
│ └── notebook.py
├── poc/
│ ├── poc.py
│ ├── rce_poc.py
│ └── requirements.txt
├── SAFETY.md
├── README.md
└── .gitignore
| الملف | الغرض |
|---|---|
docker-compose.yml | يحدد خدمات marimo الثغرية والمُصححة |
vuln/Dockerfile | يبني خدمة marimo الثغرية |
patched/Dockerfile | يبني خدمة marimo المُصححة |
vuln/notebook.py | ملف دفتر ملاحظات marimo الأدنى المستخدم بواسطة الخدمة الثغرية |
patched/notebook.py | ملف دفتر ملاحظات marimo الأدنى المستخدم بواسطة الخدمة المُصححة |
poc/poc.py | سكربت إثبات بأقل ضرر يشغل أوامر غير ضارة فقط |
poc/rce_poc.py | عميل تعلم في الوقت الفعلي لمراقبة السلوك الطرفي في المختبر المحلي |
poc/requirements.txt | تبعيات Python لسكربتات PoC |
SAFETY.md | قواعد السلامة وحدود النطاق |
الجهاز المضيف
127.0.0.1:8081 ─────► حاوية vuln
marimo 0.20.4
/terminal/ws يقبل WebSocket غير مصادق
127.0.0.1:8082 ─────► حاوية patched
marimo 0.23.0
/terminal/ws يرفض WebSocket غير مصادق
تكشف كلتا الخدمتين عن المنفذ الداخلي 2718 الخاص بـ marimo، لكن منافذ المضيف مختلفة:
vuln -> 127.0.0.1:8081
patched -> 127.0.0.1:8082
الخدمات مرتبطة بـ 127.0.0.1 فقط. ليس المقصود تعريضها لشبكة LAN أو الإنترنت.
تم تكوين الحاويات مع عدة حواجز حماية حيثما أمكن:
- ربط المنافذ بـ 127.0.0.1 فقط
- التشغيل كمستخدم غير جذر
- إسقاط قدرات Linux
- تمكين no-new-privileges
- استخدام نظام ملفات جذر للقراءة فقط
- توفير مواقع كتابة tmpfs محدودة فقط
- عزل الخدمات داخل شبكة جسر Docker مخصصة
لا تزيل هذه الضوابط الثغرة من الخدمة الثغرية. إنها تقلل من نطاق الانفجار للعرض التوضيحي المحلي.
الافتراضات المختبرة:
- Linux x86_64 أو macOS مع Docker Desktop
- Docker Compose v2
- Python 3.9+
- اختبار محلي فقط
الأدوات المطلوبة:
docker --version
docker compose version
python3 --version
انتقل إلى دليل المشروع أو ادخل إليه:
cd cve-2026-39987
قم ببناء وتشغيل كلتا الخدمتين:
docker compose up -d --build
تحقق من أن كلتا الحاويتين تعملان:
docker compose ps
الخدمات المتوقعة:
cve-2026-39987-vuln
cve-2026-39987-patched
تحقق من إصدار الخدمة الثغرية:
docker compose exec vuln marimo --version
المتوقع:
0.20.4
تحقق من إصدار الخدمة المُصححة:
docker compose exec patched marimo --version
المتوقع:
0.23.0
أنشئ بيئة Python افتراضية:
python3 -m venv .venv
source .venv/bin/activate
قم بتثبيت التبعيات:
python -m pip install -r poc/requirements.txt
سكربت الإثبات الأساسي هو:
poc/poc.py
يحاول الاتصال بـ:
/terminal/ws
ثم يرسل أوامر إثبات غير ضارة فقط:
id
whoami
hostname
لا يتم استخدام صدور عكسي، أو كتابة ملفات، أو استمرارية، أو وصول لبيانات الاعتماد، أو أوامر تدميرية.
قم بالتشغيل:
python poc/poc.py --base-url http://127.0.0.1:8081
النتيجة المتوقعة:
[*] Target WebSocket: ws://127.0.0.1:8081/terminal/ws
[*] Sending benign proof command only: id; whoami; hostname
[+] websocket connected without credentials
[result] VULNERABLE: unauthenticated command execution observed
[proof]
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)
marimo
<container-hostname>
إذا طبع السكربت مخرجات الطرفية الخام لكنه تضمن هذه الكتلة، فإن الثغرة ما زالت مؤكدة:
CVE39987_PROOF_START
uid=10001(marimo) gid=10001(marimo) groups=10001(marimo)
marimo
<container-hostname>
CVE39987_PROOF_END
هذا يعني أن اتصال WebSocket نجح وأن مخرجات الأوامر عادت من الجلسة الطرفية.
قم بالتشغيل:
python poc/poc.py --base-url http://127.0.0.1:8082
النتيجة المتوقعة:
[*] Target WebSocket: ws://127.0.0.1:8082/terminal/ws
[*] Sending benign proof command only: id; whoami; hostname
[-] websocket connection rejected/failed: Handshake status 403 Forbidden
[result] not exploitable by this unauthenticated check
هذا يُظهر أن الخدمة المُصححة ترفض الوصول غير المصادق قبل إنشاء جلسة طرفية.
الملف أدناه مخصص للتعلم المحلي فقط:
poc/rce_poc.py