
مختبر عملي يوضح حقن OGNL في Apache Struts2 (CVE-2017-5638) مع تحليل منهجي للنظام، واستغلال الثغرة، وتجاوز الصندوق الرملي، وتقنيات ما بعد الاستغلال لأغراض تعليم اختبار الاختراق.

بعد بدء الحاوية، تُظهر سجلات Struts2 أن التطبيق يقوم بتحميل ملفات التكوين المألوفة مثل struts-default.xml و struts-plugin.xml و struts.xml. وهذا يؤكد أن الإطار المستخدم هو Apache Struts2.

نقطة بارزة تكمن في السطر:
Choosing bean (jakarta) for (org.apache.struts2.dispatcher.multipart.MultiPartRequest)
يشير هذا السطر إلى أن Struts2 يختار محلل Jakarta multipart (محلل بيانات الرفع المتعدد الأجزاء) لمعالجة الطلبات من نوع multipart/form-data، الذي يُستخدم عادةً في وظيفة رفع الملفات.
هذه إشارة حاسمة عند تحليل S2-045 / CVE-2017-5638، حيث أن هذه الثغرة مرتبطة بالعملية التي يعالج بها Struts2 الأخطاء عند تحليل طلبات multipart، خاصةً مع عنوان Content-Type غير صالح.
ومع ذلك، فإن هذا السجل يثبت فقط أن التطبيق يستخدم Struts2 ومعالج multipart من نوع Jakarta. لا يزال غير كافٍ لاستنتاج أن التطبيق معرض للخطر بالتأكيد. للتأكيد، نحتاج إلى تحديد إصدار struts2-core ومقارنته بنطاق الإصدارات المتأثرة.



عند الوصول إلى خدمة الويب باستخدام curl، تُظهر ترويسات الاستجابة أن التطبيق يعمل على Jetty 9.2.11.v20150529. هذه المعلومات تساعد في تحديد البيئة التي يعمل عليها التطبيق (حاوية servlet)، لكنها لا تكشف بشكل مباشر عن إصدار Struts2.
تقوم واجهة الويب بإرجاع صفحة Struts2 Showcase - Fileupload sample، مع نموذج رفع يستخدم:
method="POST" enctype="multipart/form-data" action="/upload.action"
يتوافق هذا مع السجل السابق حيث اختار Struts2 jakarta لـ MultiPartRequest: التطبيق بالفعل لديه تدفق معالجة رفع الملفات عبر multipart/form-data.
⇒ تفكير: نقطة النهاية /upload.action تستخدم multipart/form-data، مما يتطابق مع الآلية التي يعالج بها Struts2 الطلبات عبر Jakarta MultiPartRequest. هذه علامة تعزز الشك في S2-045/CVE-2017-5638، لكننا نحتاج إلى تحديد إصدار Struts2 قبل استنتاج أن التطبيق معرض للخطر. بعد ذلك، لا يزال التحقق الأعمق مطلوبًا فيما يتعلق بـ إصدار struts2-core وكيفية معالجة التطبيق للأخطاء عند استلام Content-Type غير صالح.
بعد تحديد أن التطبيق لديه نقطة نهاية رفع باستخدام multipart/form-data، فإن الخطوة التحليلية التالية هي العثور على الإصدار الفعلي لـ Struts2. هذا أمر بالغ الأهمية لأن العلامات السابقة فقط أظهرت أن التطبيق لديه آلية متعلقة برفع multipart، وهو غير كافٍ بعد لاستنتاج أنه معرض للخطر.

بعد تحديد الحاوية الصحيحة التي تخدم نقطة النهاية 8001، يتم إجراء فحص المكتبات مباشرة داخل الحاوية project1-lab01-1.
النتيجة وجدت ملف struts2-core:
/root/.m2/repository/org/apache/struts/struts2-core/2.3.30/struts2-core-2.3.30.jar
من هذا المسار، يمكننا تحديد أن التطبيق يستخدم Apache Struts2 2.3.30.

مقارنة هذا مع الثغرة المنشورة CVE-2017-5638 / S2-045، فهي تؤثر على العديد من إصدارات Struts2 القديمة، بما في ذلك فرع 2.3.x قبل التصحيح. عند دمجها مع:
Framework: Apache Struts2 Version: 2.3.30 Multipart parser: Jakarta Endpoint: /upload.action Content-Type: multipart/form-data
تصبح سلسلة شروط التحليل أوضح:
Struts2 Version 2.3.30 < Version 2.3.32
+ Jakarta multipart parser + upload endpoint
→ The application is within the strong suspicion range of S2-045/CVE-2017-5638
ومع ذلك، من منظور تحليلي، الإصدار المعرض للخطر هو مجرد دليل على إمكانية التأثر. لتأكيد ذلك على المستوى السلوكي، نحتاج إلى إرسال طلب multipart شاذ ومراقبة الاستجابة/السجلات لمعرفة ما إذا كان يدخل في فرع معالجة الأخطاء لمحلل Struts2 multipart.
⇒ تفكير: عند هذه النقطة، لم يعد الأمر مجرد تحديد الإطار؛ الإصدار 2.3.30 يؤكد أن التطبيق يقع ضمن نطاق الإصدارات المتأثرة بـ S2-045/CVE-2017-5638. الخطوة المتبقية هي التحقق من سلوك معالجة أخطاء multipart لإكمال سلسلة الأدلة.

نحتاج إلى التحقق مما إذا كانت الطلبات المرسلة إلى /upload.action تمر فعلاً عبر آلية معالجة multipart الخاصة بـ Struts2. هنا، أستخدم أمر curl مع الخيار -F لاختبار آلية المعالجة. والنتيجة المُعادة مقسمة إلى أجزاء:
`
⇒ يثبت الطلب الصحيح أن /upload.action يمر بالفعل عبر آلية رفع multipart لأن curl -F يُنشئ multipart/form-data ويمكن للخادم تحليل كل جزء من الطلب.


بعد إرسال طلب يُعلن Content-Type على أنه multipart/form-data ولكن مع جسم لا يتوافق مع بنية multipart، لا يزال الخادم يُعيد HTTP 200 OK. ومع ذلك، فإن حقول ContentType و FileName و File و Caption كلها فارغة. عند فحص سجلات Docker، نلاحظ عدم وجود boundary (سلسلة فاصل بين الأجزاء في multipart)، ولا يزال العميل يتلقى HTTP 200 OK. ولكن في الواقع، Struts2 واجه خطأ أثناء معالجة الطلب.
يثبت هذا سلسلة الأدلة:
Faulty multipart request
→ Struts2 wraps request
→ MultiPartRequestWrapper is called
→ JakartaMultiPartRequest parses request
→ FileUploadException due to missing boundary
⇒ يتطابق مع المكونات المتعلقة بـ S2-045/CVE-2017-5638. وبالتالي، تصبح سلسلة الشروط أكثر اكتمالًا: إصدار معرض للخطر، محلل Jakarta، نقطة نهاية رفع، والطلب الخاطئ يدخل في فرع معالجة multipart الصحيح.
النقطة الحاسمة في S2-045/CVE-2017-5638 لا تكمن فقط في فشل محلل multipart. خطأ المحلل هو مجرد الشرط المسبب الأولي. الجزء الخطير يكمن في كيفية معالجة Struts2 لرسالة الخطأ لاحقًا. مع إصدارات Struts2 المتأثرة، عندما يواجه محلل multipart خطأ، يمكن تغذية محتوى الخطأ في آلية معالجة الرسائل الخاصة بـ Struts2. إذا كان المهاجم يتحكم في جزء من البيانات التي تظهر في الخطأ، خاصة من ترويسة Content-Type، يمكن تقييم تلك البيانات بواسطة Struts2 عبر OGNL (Object-Graph Navigation Language - لغة التعبير الخاصة بـ Struts/XWork).
تفكير:
Anomalous Content-Type
→ Jakarta multipart parser parsing error
→ Struts2 generates/logs error message
→ error message goes through expression evaluation mechanism
→ if malicious OGNL is present, it can lead to RCE
تحديد أن الهدف يستخدم Struts2، وبما أن Struts2 يستخدم OGNL كمحرك تعبيرات، فإن البحث في PayloadsAllTheThings يُظهر أن حقن new String(@java.lang.Runtime@getRuntime().exec("id").getInputStream().readAllBytes()) في Struts2 سيفشل لأن Struts2 لديه صندوق حماية يمنع الوصول إلى java.lang.Runtime.

⇒ تجميع الحمولة الموجودة في هيكل استغلال Struts2:
تشغيل محلل Jakarta
(#_="multipart/form-data")
تجاوز صندوق حماية Struts2 (مطلوب)