
مسارات التهيئة الطرفية (terminal bootstrap routes) الخاصة بالمسؤولين فقط والتي يتم التحقق منها فقط لحالة تسجيل الدخول، تسمح لأعضاء الفريق العاديين بتشغيل الواجهة الخلفية الطرفية الفورية لـ Coolify وتنفيذ الأوامر على خوادم الفريق.
مسار البوتستراب الطرفي المخصص للمسؤولين فقط يتحقق من حالة تسجيل الدخول فقط، مما يسمح لعضو فريق عادي بتشغيل نظام الطرفية الخلفي في الوقت الفعلي لـ Coolify وتنفيذ أوامر على خوادم الفريق.
وجدت هذه المشكلة أثناء مراجعة Coolify، وهو نظام PaaS مفتوح المصدر مستضاف ذاتيًا، مع سؤال أمني مباشر في ذهني:
هل يتم تنفيذ الوصول إلى الطرفية فعليًا على مستوى حدود الثقة الخلفية، أم فقط في واجهة المستخدم؟
في هذه الحالة، كانت الإجابة سيئة.
كانت نية Coolify تقييد الوصول إلى الطرفية لمسؤولي الفريق وأصحاب المشروع فقط، ولكن مسارات البوتستراب الطرفية في الوقت الفعلي كانت تتحقق فقط من أن المستخدم مسجل الدخول. وهذا سمح لعضو فريق ذو صلاحيات منخفضة باستيفاء شيكات الثقة الخاصة بـ websocket الطرفية والوصول إلى تنفيذ الأوامر على خوادم الفريق.
قمت بالتحقق من ذلك من البداية إلى النهاية في بيئة مختبرية محلية تم بناؤها من الإصدار الضعيف، ثم قمت بالإبلاغ عنه بشكل خاص. تم تعيين CVE-2026-34048 للمشكلة مع:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Coolify: Coolify على GitHub
CVE: CVE-2026-34048
أثرت هذه المشكلة على Coolify، وهو نظام PaaS مفتوح المصدر مستضاف ذاتيًا. ينص الموقع الرسمي لـ Coolify على أنه يخدم أكثر من 3,641 عميلًا سحابيًا، ويقدم نفسه كمنصة لنشر المواقع وقواعد البيانات وتطبيقات الويب و أكثر من 280 خدمة بنقرة واحدة. كما يذكر سجل التغييرات الرسمي للإصدار v4.0 أن آلاف الشركات والأشخاص استخدموا Coolify في الإنتاج لمدة 1-2 سنوات.
photo0
جلسة عضو فريق ذو صلاحية منخفضة -> نقطتا /terminal/auth و /terminal/auth/ips تتحققان فقط من حالة تسجيل الدخول -> websocket في الوقت الفعلي يثق في هذه الاستجابات -> يقوم العضو بتعداد خادم الفريق و UUID المفتاح SSH المرئي -> /terminal/ws يقبل الجلسة -> يتم إنشاء PTY مدعوم بـ SSH -> وصول إلى شيل على خادم الفريق
Coolify هو نظام PaaS مستضاف ذاتيًا ومنصة نشر.
يدير:
هذه القدرة الأخيرة هي المهمة هنا.
بمجرد أن تفتح المنصة طرفية للمضيفين المُدارين، فإن نموذج الترخيص الخاص بها لم يعد مجرد منطق تطبيق. بل يصبح حدود ثقة للبنية التحتية.
السؤال المهم لم يكن ما إذا كانت صفحة /terminal تبدو مخصصة للمسؤولين فقط.
السؤال الحقيقي كان:
هل يقوم مسار الطرفية الخلفي بالفعل بتطبيق نفس حدود الترخيص عند إنشاء جلسة websocket؟
في هذه الحالة، لم يفعل.
تعد ميزات الطرفية من أعلى الأسطح قيمة في برامج البنية التحتية.
لماذا؟
لأن أي عدم تطابق بين:
يمكن أن يحول مستخدم تطبيق عادي إلى مشغل قادر على استخدام الشيل.
هذا بالضبط هو سبب وجوب اختبار هذا السطح.
لم أكن أبحث عن أعطال عشوائية أو أخطاء صلاحيات تجميلية.
كنت أبحث عن فئة أقوى من الفشل:
هل تعتمد ميزة مخصصة للمسؤولين فقط على فحص ثقة خلفي أضعف مما توحي به واجهة المستخدم؟
كان هذا هو السؤال الصحيح.
لم أقترب من Coolify عن طريق اختبار عشوائي لنقاط النهاية وأمل في العثور على شيء مثير للاهتمام.
الطريق الأقوى كان تحديد الحدود الأكثر خطورة أولاً.
بالنسبة لـ Coolify، كانت تلك الحدود هي سير عمل الطرفية:
هذا جعل مسارات البوتستراب المكان المناسب للبحث.
وهناك كانت المشكلة.
كان السبب الجذري هو عدم تطابق الترخيص بين واجهة المستخدم الطرفية ومسارات بوتستراب websocket الطرفية.
في الإصدار الضعيف:
GET /terminal كان محميًا بـ can.access.terminalPOST /terminal/auth كان يتحقق فقط من auth()->check()POST /terminal/auth/ips كان يتحقق فقط من auth()->check()هذا يعني أن واجهة المستخدم كانت مقيدة بترخيص الطرفية، بينما حدود الثقة الخلفية كانت مقيدة بوجود جلسة مصادقة بسيطة.
ثم وثقت خدمة الوقت الفعلي في هذين المسارين تمامًا.
في docker/coolify-realtime/terminal-server.js:
verifyClient() أرسل طلب POST إلى /terminal/auth/terminal/auth/ipsهذه هي سلسلة الخلل بأكملها.
لأن عضو فريق عادي يمكنه بناء المدخلات المطلوبة من سطح التطبيق العادي:
/servers يعرض UUIDs الخوادم المرئية/server/{uuid} يعرض ip و user و port في حقول نموذج معروضة/security/private-key يعرض UUIDs المفاتيح الخاصة المرئية للفريق/var/www/html/storage/app/ssh/keys/ssh_key@<uuid>
لذا كان مسار الاستغلال واضحًا:
/terminal/auth/terminal/auth/ips/terminal/wsهذا ليس عدم تطابق نظري. هو فشل ترخيص خلفي عملي.
الفرق المهم هو ثقة الخلفية وتنفيذ الأوامر.
الكثير من الخلل يبدو مثل:
هذا وحده ليس كافيًا.
السؤال الحقيقي هو:
هل يمكن للمستخدم ذو الصلاحية المنخفضة أن يستوفي شيكات الثقة الخلفية المهمة؟
هنا، كانت الإجابة نعم.
لم يكن هذا:
بل كان:
لهذا كانت مشكلة أمنية حقيقية.
قمت بالتحقق من ذلك في مختبر محلي محكوم مبني من:
06f60c9a98bead0c932c6adf7fd43a45d9149048
استخدم المختبر:
http://127.0.0.1:18000[email protected]localhost -> coolify-testing-host:22 كـ rootsshws://127.0.0.1:6002/terminal/wsحساب العضو لم يكن مقصودًا له الوصول إلى الطرفية من خلال واجهة المستخدم العادية الخاصة بالمسؤول.
هذا أنشأ حدود الأمان المتوقعة.
باستخدام جلسة العضو، أرسلت:
POST /terminal/authPOST /terminal/auth/ipsكلاهما نجح.
/terminal/auth/ips أعاد المضيفين المصرح لهم طرفيًا بما في ذلك:
coolify-testing-host
host.docker.internal
localhost
127.0.0.1
هذا أثبت أن مسارات البوتستراب الخلفية تثق في جلسة العضو.
من صفحات عادية مصادق عليها، يمكن لنفس العضو تعداد:
كان هذا كافيًا لقيادة مسار الطرفية دون الحاجة إلى الكشف عن مادة المفتاح السري.
باستخدام نفس الجلسة المصادقة ورمز XSRF، اتصلت بـ:
ws://127.0.0.1:6002/terminal/ws
استخدمت الحمولة نفس تنسيق الأمر الذي تتوقعه خلفية الطرفية:
{"command":["timeout 30 ssh -i /var/www/html/storage/app/ssh/keys/ssh_key@ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o PasswordAuthentication=no -o ConnectTimeout=10 -o ServerAliveInterval=5 -o RequestTTY=no -o LogLevel=ERROR -p '22' 'root'@'coolify-testing-host' 'bash -se' << \\P0C\nprintf '__COOLIFY_POC_BEGIN__\\n'; id; whoami; hostname; printf '__COOLIFY_POC_END__\\n'\nP0C"]}
أعاد websocket:
pty-ready
__COOLIFY_POC_BEGIN__
uid=0(root) gid=0(root) groups=0(root)
root
efa027413801
__COOLIFY_POC_END__
كان هذا هو الإثبات المهم.
ليس فقط:
بل تنفيذ فعلي للأوامر على المضيف المُدار من خلال مسار الطرفية المخصص للمسؤولين فقط.
جزء واحد من هذه السلسلة كان سيكون مثيرًا للاهتمام بالفعل.