الحذف غير الآمن

فئة OWASP: MASVS-CODE: جودة الرمز

نظرة عامة

عند تخزين كميات كبيرة من بيانات كائنات Java أو نقلها، غالبًا ما يكون من الأفضل تسلسل البيانات أولاً. بعد ذلك، سيتم إلغاء تسلسل البيانات من خلال التطبيق أو النشاط أو مقدّم المحتوى الذي سيتولى معالجة البيانات. في الظروف العادية، يتم تسلسل البيانات ثم إلغاء تسلسلها بدون أي تدخّل من المستخدم. ومع ذلك، يمكن أن يسيء أحد الجهات المسيئة استخدام علاقة الثقة بين عملية إلغاء التسلسل والكائن المقصود، على سبيل المثال، من خلال اعتراض الكائنات المتسلسلة وتغييرها. سيسمح ذلك للجهة المسيئة بتنفيذ هجمات، مثل رفض الخدمة وتصعيد الامتيازات وتنفيذ الرموز البرمجية عن بُعد.

في حين أنّ فئة Serializable هي طريقة شائعة لإدارة نشر البيانات على نحو متسلسِل، يتضمّن Android فئة خاصة به لمعالجة نشر البيانات على نحو متسلسِل تُعرف باسم Parcel. باستخدام فئة Parcel، يمكن تسلسل بيانات الكائن إلى بيانات تدفق بايت وتعبئتها في Parcel باستخدام واجهة Parcelable. يسمح ذلك بنقل Parcel أو تخزينه بكفاءة أكبر.

ومع ذلك، يجب توخي الحذر عند استخدام فئة Parcel، لأنّها مصمّمة لتكون آلية نقل بين العمليات عالية الكفاءة، ولكن لا يجب استخدامها لتخزين الكائنات المتسلسلة في وحدة التخزين المحلية الثابتة لأنّ ذلك قد يؤدي إلى حدوث مشاكل في توافق البيانات أو فقدانها. عند الحاجة إلى قراءة البيانات، يمكن استخدام واجهة Parcelable لإلغاء تسلسل Parcel وتحويلها مرة أخرى إلى بيانات كائن.

هناك ثلاثة متجهات أساسية لاستغلال إلغاء التسلسل في Android:

  • الاستفادة من افتراض المطوّر الخاطئ بأنّ إلغاء تسلسل الكائنات الناتجة عن نوع فئة مخصّصة آمن. في الواقع، يمكن استبدال أي كائن مصدره أي فئة بمحتوى ضار قد يتداخل، في أسوأ السيناريوهات، مع أدوات تحميل الفئات نفسها أو فئات أخرى. يأخذ هذا التداخل شكل إدخال قيم خطيرة قد تؤدي، وفقًا لغرض الفئة، على سبيل المثال، إلى استخراج البيانات أو الاستيلاء على الحساب.
  • استغلال طرق إلغاء التسلسل التي تُعتبر غير آمنة حسب التصميم (على سبيل المثال، CVE-2023-35669، وهو خلل في تصعيد الامتيازات المحلية سمح بإدخال رمز JavaScript عشوائي من خلال متّجه إلغاء تسلسل الرابط المؤدي إلى محتوى معيّن)
  • استغلال الثغرات في منطق التطبيق (على سبيل المثال، CVE-2023-20963، وهو خلل في تصعيد الامتيازات المحلية سمح لتطبيق بتنزيل الرمز وتنفيذه في بيئة مميزة من خلال خلل في منطق حزمة WorkSource في Android).

التأثير

يمكن أن يكون أي تطبيق يلغي تسلسل البيانات المتسلسلة غير الموثوق بها أو الضارة عرضة لهجمات تنفيذ الرموز البرمجية عن بُعد أو هجمة حجب الخدمة.

الخطر: إلغاء تسلسل الإدخال غير الموثوق به

يمكن للمهاجم استغلال عدم التحقق من الحزمة في منطق التطبيق لإدخال كائنات عشوائية يمكن أن تجبر التطبيق، بعد إلغاء تسلسلها، على تنفيذ رمز ضار قد يؤدي إلى رفض الخدمة (DoS) وتصعيد الامتيازات وتنفيذ الرموز البرمجية عن بُعد (RCE).

قد تكون هذه الأنواع من الهجمات غير واضحة. على سبيل المثال، قد يحتوي التطبيق على هدف يتوقّع مَعلمة واحدة فقط، وسيتم إلغاء تسلسلها بعد التحقق من صحتها. إذا أرسل مهاجم مَعلمة ثانية غير متوقّعة وضارة بالإضافة إلى المَعلمة المتوقّعة، سيؤدي ذلك إلى إلغاء تسلسل جميع كائنات البيانات التي تم إدخالها لأنّ الهدف يعامل البيانات الإضافية على أنّها Bundle. قد يستغل مستخدم ضار هذا السلوك لإدخال بيانات كائن قد تؤدي، بعد إلغاء تسلسلها، إلى تنفيذ الرموز البرمجية عن بُعد أو تعريض البيانات للخطر أو فقدانها.

الإجراءات المخفّفة

كأفضل ممارسة، افترض أنّ جميع البيانات المتسلسلة غير موثوق بها وقد تكون ضارة. لضمان سلامة البيانات المتسلسلة، عليك إجراء عمليات تحقّق من البيانات للتأكّد من أنّها الفئة والتنسيق الصحيحَين اللذين يتوقّعهما التطبيق.

يمكن أن يكون الحلّ المناسب هو تنفيذ نمط التطلّع إلى الأمام لـ java.io.ObjectInputStream المكتبة. من خلال تعديل الرمز المسؤول عن إلغاء التسلسل، يمكنك التأكّد من أنّه يتم إلغاء تسلسل مجموعة محدّدة من الفئات فقط ضمن الهدف.

اعتبارًا من Android 13 (مستوى واجهة برمجة التطبيقات 33)، تم تعديل عدة طرق ضمن فئة Intent تُعتبر بدائل أكثر أمانًا للطرق القديمة التي تم إيقافها نهائيًا الآن لمعالجة الحزم. تُجري هذه الطرق الجديدة الأكثر أمانًا من حيث النوع، مثل getParcelableExtra(java.lang.String, java.lang.Class) و getParcelableArrayListExtra(java.lang.String, java.lang.Class)، عمليات تحقّق من نوع البيانات لرصد نقاط الضعف في عدم التطابق التي قد تؤدي إلى تعطُّل التطبيقات ويمكن استغلالها لتنفيذ هجمات تصعيد الامتيازات، مثل CVE-2021-0928.

يوضّح المثال التالي كيفية تنفيذ إصدار آمن من فئة Parcel:

لنفترض أنّ الفئة UserParcelable تنفّذ Parcelable وتنشئ مثيلاً لبيانات المستخدم التي يتم بعد ذلك كتابتها إلى Parcel. يمكن بعد ذلك استخدام طريقة الأكثر أمانًا من حيث النوع لـ readParcelable لقراءة الحزمة المتسلسلة:

Kotlin

val parcel = Parcel.obtain()
val userParcelable = parcel.readParcelable(UserParcelable::class.java.classLoader)

Java

Parcel parcel = Parcel.obtain();
UserParcelable userParcelable = parcel.readParcelable(UserParcelable.class, UserParcelable.CREATOR);

لاحظ في مثال Java أعلاه استخدام UserParcelable.CREATOR ضمن الطريقة. تخبر هذه المَعلمة المطلوبة طريقة readParcelable بنوع البيانات المتوقّع، وهي أكثر فعالية من الإصدار الذي تم إيقافه الآن من طريقة readParcelable.

المخاطر المحدّدة

يجمع هذا القسم المخاطر التي تتطلب استراتيجيات تخفيف غير عادية أو تم تخفيفها على مستوى معيّن من حزمة تطوير البرامج (SDK)، وهي مدرَجة هنا من أجل الاكتمال.

الخطر: إلغاء تسلسل الكائن غير المرغوب فيه

سيؤدي تنفيذ واجهة Serializable ضمن فئة إلى تنفيذ جميع الأنواع الفرعية للفئة للواجهة تلقائيًا. في هذا السيناريو، قد ترث بعض الكائنات الواجهة المذكورة أعلاه، ما يعني أنّه سيتم معالجة كائنات معيّنة غير مخصّصة لإلغاء التسلسل. قد يؤدي ذلك إلى زيادة سطح الهجوم عن غير قصد.

الإجراءات المخفّفة

إذا ورثت فئة واجهة Serializable، وفقًا لإرشادات OWASP، يجب تنفيذ طريقة readObject على النحو التالي لتجنُّب إلغاء تسلسل مجموعة من الكائنات في الفئة:

Kotlin

@Throws(IOException::class)
private final fun readObject(in: ObjectInputStream) {
    throw IOException("Cannot be deserialized")
}

Java

private final void readObject(ObjectInputStream in) throws java.io.IOException {
    throw new java.io.IOException("Cannot be deserialized");
}

الموارد