
تقرير التحقيق عن ثغرات Struts2 S2-045, S2-055 وثغرات Jackson CVE-2017-7525, CVE-2017-15095
تم نشر مقال ملخص يلخص النقاط الرئيسية ويجعله سهل القراءة. يُوصى به لمن يرغب في معرفة نظرة عامة أولاً أو لمن ليس لديه وقت كافٍ.
في 1 ديسمبر 2017، تم إصدار تحديث أمني لـ Struts2. قبل الإصدار، كانت هناك مناقشات في القائمة البريدية تفيد بأن ثغرات Jackson (مكتبة JSON الشائعة في Java) مرتبطة بها، وكان الكاتب، الذي يستخدم Jackson في الأنظمة والأدوات الداخلية للشركة، مهتمًا بمعرفة التفاصيل الدقيقة.
في الواقع، تم إصلاح مشكلتين أمنيتين في المحتوى المنشور. الثغرة في jackson-databind، وهي مكون من Jackson، تؤثر فقط على S2-055.
في REST plugin، يبدو أنه تم تضمين معالجات تستخدم JSON-lib ومعالجات تستخدم Jackson منذ البداية، ويمكن للمستخدم الاختيار بينهما. في S2-054، تم تبديل المعالج الافتراضي إلى Jackson، ثم في S2-055 تم تحديث إصدار Jackson القديم إلى الأحدث، وهذا هو جوهر التصحيح الحالي.
إذن، ما هي ثغرة CVE-2017-7525 بالضبط؟ بما أن الكاتب يستخدم Jackson غالبًا عند معالجة JSON في Java، فقد قضى عطلة نهاية الأسبوع في 2 و 3 ديسمبر للتحقيق في هذه المسألة، وهذا هو محتوى هذه المقالة.
بيئة الكاتب المستخدمة للتحقق من نموذج الكود:
تم نشر شرح لـ CVE-2017-7525 في مدونة Adam Caudill.
باختصار بكلمات الكاتب نفسه، يوفر jackson-databind وظيفة لتعيين JSON إلى كائنات Java (فئة ObjectMapper).
عن طريق استدعاء ObjectMapper.enableDefaultTyping()، يصبح من الممكن التعيين باستخدام اسم فئة مضمن بشكل مخصص في JSON.
ربما شعر بعض القراء بإحساس سيء بمجرد "إمكانية تحديد اسم الفئة من إدخال JSON"، وهذا الإحساس السيئ تحقق تمامًا في CVE-2017-7525.
قبل الدخول في شرح الثغرة، دعنا نشرح لماذا تم تنفيذ هذه الوظيفة في المقام الأول.
يرجى مراجعة نموذج الكود التالي للاستخدام الأساسي لإلغاء التسلسل (deserialize) بواسطة jackson-databind. (في هذه المقالة، نستخدم Groovy في نموذج كود Jackson. من المريح أنه يمكن تبديل إصدار jackson-databind بسهولة باستخدام @Grab.)
في نموذج الكود أعلاه، يمكن ببساطة تعيين مفتاح "animal" إلى فئة Animal. وماذا عن الحالة التالية؟```java class Zoo { Animal animal; }
abstract class Animal { String name; protected Animal() { } }
class Dog extends Animal { double barkVolume; Dog() { } }
class Cat extends Animal { boolean likesCream; int lives; Cat() { } }
في هذا التكوين، يظهر نوعان: عندما يشير مفتاح "animal" إلى فئة Dog، وعندما يشير إلى فئة Cat. وبالتالي، نحتاج إلى معلومات إضافية لتحديد أي من الفئتين سيتم استخدامها للتعيين.
لحل هذه المشكلة، أضافت jackson-databind معالجة خاصة تسمح بدمج اسم الفئة المستخدمة في التعيين داخل JSON.
على سبيل المثال، كما هو موضح أدناه، يتم تحويل محتوى مفتاح "animal" إلى مصفوفة، ويتم تحديد اسم الفئة في العنصر الأول.```
{"animal":["Dog",{"name":"dog1","barkVolume":1.2}]}
وبالتالي فإن ObjectMapper.readValue() يتعرف على أن محتوى المفتاح "animal" هو من فئة Dog ويقوم بالتخطيط.
بالطبع، لا يمكن التمييز مباشرةً ما إذا كان محتوى المفتاح "animal" كان في الأصل مصفوفة أم كان يحتوي على معلومات اسم الفئة الخاصة بـ jackson-databind.
الطريقة التي تسمح بالتبديل بين هذين النمطين هي ObjectMapper.enableDefaultTyping().
هناك أيضًا طريقة لتعريف التعليق التوضيحي @JsonTypeInfo في الفئة. لمزيد من التفاصيل، راجع وثائق Jackson التالية.
فيما يلي نموذج لاستخدام طريقة ObjectMapper.enableDefaultTyping() فعليًا.
كما رأينا أعلاه، من خلال إعطاء اسم الفئة ثم خصائصها بتنسيق JSON، يمكن إنشاء أي فئة بأي خصائص، على الرغم من وجود بعض القيود. واستغلال هذه الثغرة هو ثغرة CVE-2017-7525، ويُعتقد أن الدافع وراء ذلك هو التقرير التالي.
بالنسبة لمكتبات التسلسل وإلغاء التسلسل شائعة الاستخدام في Java مثل Jackson، تم الإبلاغ عن خطر يؤدي إلى تنفيذ رمز عشوائي عن طريق التلاعب بأسماء الفئات، وتم إدراج أسماء الفئات المحددة التي تشكل خطرًا. لا أعرف ما إذا كان ذلك ردًا على ذلك، لكن من حيث التاريخ، بعد أول commit للمستودع المذكور أعلاه مباشرة، تم إنشاء Issue التالي في jackson-databind وبدأ العمل على معالجته.
كيف يبدو بالفعل بيانات JSON ورمز Java الذي يستغل هذه الثغرة؟ هناك تلميح في كود الاختبار الخاص بـ jackson-databind 2.8.9 الذي تم معالجته في هذه المشكلة:
فيما يلي نموذج كود تم تعديله بناءً على كود الاختبار هذا للتحقق من العمل.