
CVE-2020-2551 POC لاستخدامها على الإنترنت
اختبار POC (يمكن استخدامه في الاختبارات الخارجية)
python CVE-2020-2551.py [HOST] [IP]
محاولة تعديل codebase لتحميل الفئات عن بُعد (فشل)
python CVE-2020-TEST.py [HOST] [IP]
(نُشر لأول مرة على Anquanke، الرابط الأصلي)
تتطلب دراسة هذه الثغرة بعض المعرفة المسبقة، مثل CORBA و RMI.
باختصار:
CORBA هي مجموعة من المعايير التقنية التي وضعتها OMG للتطبيقات الموزعة، وتستخدم IDL لدعم متعدد اللغات، وتتواصل بين العميل والخادم باستخدام بروتوكول IIOP.
RMI هي تقنية تطبيقات موزعة أخرى، ويمكن استخدام JNDI لتبسيط التطبيق في JAVA، حيث يتواصل العميل مع الخادم باستخدام بروتوكول JRMP، ولكن في weblogic يستخدم RMI بروتوكول T3، وقد تم كشف العديد من الثغرات حول هذا سابقًا.
يجمع RMI-IIOP بين مزايا كل من RMI و CORBA، وينشر تطبيقات RMI عبر بروتوكول IIOP.
وقد ذكرت الوثائق الرسمية أيضًا:
يمكن لكائنات خادم RMI استخدام بروتوكول IIOP والتواصل مع كائنات عميل CORBA المكتوبة بأي لغة.
بدون ذكر weblogic حاليًا، لننظر أولاً في كيفية كتابة مثال RMI-IIOP:
يمكن الرجوع إلى كود العميل في مقال Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事儿(上) و مشروع الاختبار ، يمكنك تجميع HelloClient و HelloServer بنفسك، أو استخدام الملفات المترجمة مسبقًا في مشروع الاختبار.
قم بتشغيل خادم الأسماء من سطر الأوامر (مرفق مع Java):
start orbd -ORBInitialPort 1050
قم بتشغيل خادم HelloServer من سطر الأوامر مع تفعيل التصحيح عن بُعد. لمزيد من المعلومات حول كيفية إجراء التصحيح عن بُعد باستخدام IDEA، راجع الطريقة المذكورة في بداية هذه المقالة.
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 HelloServer
بالطبع، يمكنك أيضًا تشغيله بدون تصحيح عن بُعد ورؤية النتيجة مباشرةً، فقط قم بالتشغيل:
java HelloServer
قم بتشغيل العميل من سطر الأوامر:
Java HelloClient
عندها ستظهر الآلة الحاسبة. إذا نجح التصحيح عن بُعد، يمكنك رؤية مكدس الاستدعاءات التالي:

تنفيذ الأمر في EvilMessage.readObejct()

ملاحظة جانبية: مقالات حول تثبيت و تصحيح أخطاء WebLogic.
فماذا عن RMI-IIOP في weblogic؟ مقال حول RMI-IIOP في Java يذكر استخدام RMI-IIOP في weblogic، تم إجراء بعض الأبحاث بناءً عليه. يشرح استخدام RMI عبر IIOP في WebLogic عدة طرق لاستخدام عميل RMI-IIOP في Weblogic، بما في ذلك:
يبدو أن الفرق بين الطريقتين الأوليين هو فقط في إعداد JNDI_FACTORY.

عند دراسة إلغاء تسلسل T3 في weblogic سابقًا، قمت بنشر تطبيق Helloserver على weblogic، والذي يحتوي على طريقة sayhello() يمكن استغلالها. حاولت تعيين نوعي JNDI_FACTORY واستدعاءها، ونجحت الطريقة الثانية في استدعاء sayHello().
ثم قمت بتعديل POC لبروتوكول T3 في weblogic، في الواقع قمت فقط بتغيير RMI إلى IIOP، ووجدت أن سلسلة استغلال jtaTransactionManager قد نفذت بنجاح، وأرسل طلب jrmp إلى jrmplisten المحلي.

لننظر إلى حركة المرور، عند استدعاء طريقة remove()، يتم إرسال طلب remove__java_lang_Object، وتحتوي حركة المرور على بيانات ضارة، ولكن لم يتم العثور على التوقيع aced.

أفترض أنه بعد تحليل خاص من جانب الخادم، يتم إلغاء تسلسل البيانات. بالنظر إلى مكدس الاستدعاءات، يمكن رؤية أن الجزء الثاني من سلسلة التنفيذ مشابه جدًا لسلسلة تنفيذ RMI-IIOP الأصلية. الجزء السابق يبدأ من CDRInputStream.read_value()، وهنا يبدأ من weblogic.InputStream.read_value()، (نقطة read_value تم ذكرها أيضًا في عرض عام 2019).

هنا، سيتم التعامل مع الطلب أولاً بواسطة clusterableServerRef.invoke()، وبناءً على invoker مختلف يتم استدعاء this.invoker.invoke()، ثم يتم استدعاء Mejb_dj5nps_HomeImpl_WLSkel.invoke()، وبما أنه "remove"، يتم الدخول إلى الفرع case 6 واستدعاء IIOPInputStream.readObject()، في طريقة read_value() يتم تحليل بيانات IIOPInputStream ويتم تشغيل إلغاء التسلسل. هذا هو POC باستخدام طريقة remove().
ذكر تحليل الأخ Lucifaer في مقالته استخدام طريقة bind() للاستغلال، وهي أيضًا طريقة الاستغلال السائدة على الإنترنت. دعنا نتابع مكدس الاستدعاءات.

كما في السابق، سيتم التعامل مع الطلب أولاً بواسطة clusterableServerRef.invoke()، وبناءً على invoker مختلف يتم استدعاء this.invoker.invoke()، هنا يتم استدعاء CobraServerRef.invoke()، ثم في _NamingContextAnyImplBase._invoke()، نظرًا لأن va1 هو "bind_any"، يتم الدخول إلى الفرع case 0 واستدعاء IIOPInputStream.read_any()، والتي ستستدعي في النهاية IIOPInputStream.read_value() لتشغيل إلغاء التسلسل. ذكرت سابقًا أنه لم يتم رؤية التوقيع aced في حركة المرور، وذلك لأن هناك طريقة تحليل خاصة في IIOPInputStream. شكل hex-value لـ IIOPInputStream كالتالي، والذي يحتوي على اسم الفئة ومعلومات الحقول:

في النهاية، سيتم استدعاء طريقة readObejct() الخاصة بالفئة الضارة.

بالنظر إلى التصحيح، وجدت أنه في نفس الموقع مثل تصحيح استغلال إلغاء تسلسل T3 لعام 2015.

في اختبار تحليل ثغرة CVE-2020-2551 في WebLogic، نرى أن موقع تصفية فئات CVE-2020-2551 أيضًا في فئة weblogic.iiop.Utils.

ولكن في الاختبار المحلي، WebLogic 10.3.6 بعد تطبيق تصحيح 2015، لم يتم تشغيل دالة isBlacklisted() (على الرغم من أن isBlacklisted() يتم استدعاؤها للتحقق من القائمة السوداء في كل من MsgAbbrevInputStream و InboundMsgAbbrev، وهذا غريب...).

تضمن تصحيح CVE-2020-2551 هذه المرة إضافة طريقة filter() للتحقق من الصلاحية في weblogic.iiop.Utils.LoadClass().

تقوم القائمة السوداء بتصفية الفئات الضارة، بما في ذلك الفئة الأصلية لـ JtaTransactionManager وهي com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager، وهذه الفئة مرفقة مع weblogic وهي خطيرة جدًا. أثناء النظر إلى التصحيح، خطرت لي فكرة: لأن التحقق في السطر 606 يتم بعد LoadClass()، فماذا لو تم تحميل الفئة وتنفيذ كتلة ثابتة ضارة أثناء تحميل className؟ هل يمكن تجنب الدفاع بهذه الطريقة؟ سنتحدث عن هذا لاحقًا.
POC المكتوب بلغة JAVA يعاني من مشاكل في الشبكة، حيث أنه يعمل بشكل جيد عند استهداف خدمة weblogic محلية، لكنه يفشل عند استهداف حاوية docker أو آلة خارجية. مقالات تحليل هذه المشكلة:
سنقوم بتصحيح أخطاء POC، يمكنك الرجوع إلى POC الخاص بـ remove() السابق، أو Y4er. تذكر المقالتان السابقتان طريقتين للحل:
لقد جربت كلا الطريقتين. بعد إعادة تعبئة weblogic، حدث خطأ java.lang.NoSuchMethodError: weblogic.security.subject.SubjectManager.installCESubjectManager، لكن لم أجد حلاً.

لذا حاولت محاكاة بروتوكول IIOP. أولاً، قمت بوضع نقطة توقف في POC للتصحيح.

وجدت أنه عند استدعاء new InitialContext(env)، يتم استدعاء EndPointImpl.sendReceive() لإرسال واستقبال حزمتين.

يحتوي LocateReply على معلومات IOR. من المهم فهم ما هو IOR. دوره هو: عندما يتفاعل عميل RMI-IIOP مع كائن الخادم باستخدام بروتوكول IIOP، فإنه يوفر host و port اللازمين لاتصال IIOP، بالإضافة إلى Object_key في الجزء المظلل لتحديد كائنات مختلفة على الخادم.

عند محاكاة بروتوكول IIOP، نحتاج إلى التركيز على Object_key. host و ip لا يؤثران في الواقع. في بداية الاختبار، قمت بإعادة إرسال جميع الحزم، وعند إرسال resolve_any، تم إرجاع location forward.

تشرح الوثائق الرسمية لـ GIOP أنه عند location forward، فإن Object_key قد يتغير، وقد يختلف Object_key الذي يتم إرجاعه في كل طلب (Object_key المقصود هنا هو key address في حزمة البيانات). كما ذكرنا سابقًا، يُستخدم هذا Object_key لتحديد الكائن الذي يتم الاتصال به عند استخدام بروتوكول IIOP، ويجب الحصول على هذه القيمة ديناميكيًا من LocateReply.

في النهاية، لم أختر محاكاة remove()، بل قمت بمحاكاة طلب IIOP الصادر عن bind()، وذلك لأن عدد الطلبات أقل. لنلق نظرة على حزمة البيانات المستخدمة محليًا بشكل صحيح.

إرسال LocateRequest، استلام البيانات، الحصول على key address من LocateReply عبر مطابقة regex.

تعيين عنوان خادم jrmp الضار يدويًا (rmi://...)، وإرسال حزمة bind_ary بفاصل زمني قدره ثانية واحدة. نظرًا لأن طلب jrmp الصادر عن سلسلة الاستغلال هذه لا يستخدم DGCClient، فإنها لا تتأثر بـ JEP290، ويمكن استغلالها عبر jrmplisten.

نجح POC في بيئة docker، يمكن استخدام بيئة vulhub SSRF، مع تعيين IP للمضيف، نجح docker في الحصول على طلب jrmp من المضيف. الكود محدد في Github.

ذكرت سابقًا فكرة استخدام codebase لتحميل كود عن بُعد لتجاوز الكشف. يجب أن يكون الطلاب الذين درسوا هجمات JNDI على دراية بأن codebase يمكن استخدامه لتحديد موقع الفئات عن بُعد. إذا كان codebase قابلًا للتحكم والبرنامج يسمح بتحميل الفئات عن بُعد، فيمكن تحميل فئة ضارة عن بُعد وتنفيذ كود ضار في الكتلة الثابتة.
من خلال قراءة الكود، نجد أن المعامل الثاني لـ weblogic.iiop.Utils.loadClass() يمثل codebase، ويتم قراءة هذا المعامل في IIOPInputStream.read_value()، وهو المعامل var8. في السطر 1659، يتم استدعاء readIndirectingRepositoryId(var8)، والذي يستدعي في النهاية weblogic.iiop.Utils.loadClass(). لتنفيذ الكود في السطرين 1644 و 1659، يجب أن يكون (var4 & 1) = 1 و (var4 & 6) = 2، وبالتالي تكون قيمة var4 هي 3.

مكدس الاستدعاءات من readIndirectingRepositoryId إلى getClassFromId، ينتهي بتنفيذ loadClass() في السطر 304.

لنلق نظرة على حزمة bind_any. هي في الواقع مكونة من GIOP Header و GIOP Request. يحتوي GIOP Request على key address (يتوافق مع LocateReply)، ServiceContextList، و stub_data. قيمة var4 هي \x7f\xff\xff\x02 في stub_data، لذا (var4 & 1) = 0 و (var4 & 6) = 2، وبالتالي لن يتم تنفيذ الكود في السطر 1644 لتعيين codebase.

نقوم بتعديل \x7f\xff\xff\x02 في المربع الأول إلى \x00\x00\x00\x03، ونضيف في المربع الثاني طول codebase وقيمته. الكود محدد في Github. يمكن ملاحظة أن هناك عملية محاذاة أيضًا، وهي نقطة صعبة. لأنه قبل قراءة معلومات الفئة التالية، يتم التحقق مما إذا كان موضع البايت التالي هو مضاعف للرقم 4. إذا لم يكن كذلك، يتم تجاهل بعض البتات. على سبيل المثال، إذا كان موضع البايت التالي هو 1، فسيتم تجاهل 3 بايتات، ويبدأ القراءة من البايت ذي الموضع 4. هذا الموضع بالنسبة لحزمة bind_any بأكملها. إذا لم يكن البايت مضاعفًا للرقم 4، يتم إضافة أصفار.

هناك مشكلة أخرى. بالنظر إلى مكدس الاستدعاءات من readIndirectingRepositoryId إلى getClassFromId، يمر عبر دالة findClassInfo(). هنا، إذا تم تحميل فئة ما بالفعل، يتم حفظ معلومات معرف الفئة، وعند استدعاء findClassInfo() يتم إرجاع معلومات الفئة مباشرةً، ولا يتم الدخول إلى دالة weblogic.iiop.Utils.getClassFromID().

لذلك عند الاختبار، يجب تغيير اسم الفئة في كل مرة.

على أي حال، في النهاية نجحت في محاكاة بروتوكول IIOP لتعديل قيمة codebase وتنفيذ دالة weblogic.iiop.Utils.getClassFromId().
لسوء الحظ، عند الحصول على RMIURLClassFinder، يتم إرجاع NULL، وتعيد دالة RMIEnvironment.getEnvironment().isNetworkClassLoadingEnabled() القيمة false.

السبب هو أن قيمة معامل _NetworkClassLoadingEnable في ServerMBeanImpl هي False.

أردت معرفة أي ملف تكوين weblogic يقوم بتعيين هذا المعامل، لكنني لم أجده...
في الواقع، أثناء تعلم هذه الثغرة، اكتشفت أن هناك حاجة إلى الكثير من المعرفة المسبقة، مثل إلغاء التسلسل في Java، RMI، JNDI، وما إلى ذلك. يمكن الرجوع إلى هذا العمود من المقالات للتعلم ذي الصلة. على الرغم من فشل محاولة استخدام codebase في النهاية، إلا أنني اكتسبت الكثير.