آب زدایی ناامن

رده OWASP: MASVS-CODE: کیفیت کد

نمای کلی

هنگام ذخیره یا انتقال حجم زیادی از داده‌های شیء جاوا، اغلب کارآمدتر است که ابتدا داده‌ها را سریالی‌سازی کنیم. سپس داده‌ها توسط برنامه، فعالیت یا ارائه‌دهنده دریافت‌کننده که در نهایت داده‌ها را مدیریت می‌کند، تحت فرآیند deserialization قرار می‌گیرند. در شرایط عادی، داده‌ها بدون هیچ گونه دخالت کاربر سریالی‌سازی و سپس deserialize می‌شوند. با این حال، رابطه اعتماد بین فرآیند deserialization و شیء مورد نظر آن می‌تواند توسط یک عامل مخرب مورد سوءاستفاده قرار گیرد که می‌تواند، به عنوان مثال، اشیاء سریالی‌سازی شده را رهگیری و تغییر دهد. این امر عامل مخرب را قادر می‌سازد تا حملاتی مانند انکار سرویس (DoS)، افزایش امتیاز و اجرای کد از راه دور (RCE) را انجام دهد.

در حالی که کلاس Serializable یک روش رایج برای مدیریت سریال‌سازی است، اندروید کلاس مخصوص به خود را برای مدیریت سریال‌سازی به نام Parcel دارد. با استفاده از کلاس Parcel ، داده‌های شیء می‌توانند به داده‌های جریان بایت سریال‌سازی شده و با استفاده از رابط Parcelable در یک Parcel بسته‌بندی شوند. این امر به Parcel اجازه می‌دهد تا به طور کارآمدتری منتقل یا ذخیره شود.

با این وجود، هنگام استفاده از کلاس Parcel باید ملاحظات دقیقی در نظر گرفته شود، زیرا قرار است یک مکانیسم انتقال IPC با راندمان بالا باشد، اما نباید برای ذخیره اشیاء سریالی شده در حافظه دائمی محلی استفاده شود زیرا این امر می‌تواند منجر به مشکلات سازگاری داده‌ها یا از دست رفتن آنها شود. هنگامی که داده‌ها نیاز به خواندن دارند، می‌توان از رابط Parcelable برای deserialize کردن Parcel و تبدیل مجدد آن به داده‌های شیء استفاده کرد.

سه بردار اصلی برای سوءاستفاده از deserialization در اندروید وجود دارد:

  • سوءاستفاده از فرض نادرست توسعه‌دهنده مبنی بر اینکه deserialize کردن اشیاء مشتق شده از یک نوع کلاس سفارشی ایمن است. در واقعیت، هر شیء که توسط هر کلاسی منبع‌دهی می‌شود، می‌تواند به طور بالقوه با محتوای مخرب جایگزین شود که در بدترین حالت، می‌تواند در همان برنامه یا سایر برنامه‌های بارگذاری کلاس اختلال ایجاد کند. این تداخل به شکل تزریق مقادیر خطرناکی است که طبق هدف کلاس، ممکن است به عنوان مثال منجر به استخراج داده‌ها یا تصاحب حساب کاربری شود.
  • سوءاستفاده از روش‌های deserialization که از نظر طراحی ناامن در نظر گرفته می‌شوند (برای مثال CVE-2023-35669 ، یک نقص افزایش امتیاز محلی که امکان تزریق کد جاوا اسکریپت دلخواه را از طریق یک بردار deserialization با لینک عمیق فراهم می‌کند)
  • Exploiting flaws in the application logic (for example CVE-2023-20963 , a local privilege escalation flaw that allowed an app to download and execute code within a privileged environment through a flaw within Android's WorkSource parcel logic).

تأثیر

هر برنامه‌ای که داده‌های سریالی شده‌ی نامعتبر یا مخرب را deserialize کند، می‌تواند در برابر حملات اجرای کد از راه دور یا انکار سرویس آسیب‌پذیر باشد.

خطر: از رده خارج کردن ورودی‌های غیرقابل اعتماد

یک مهاجم می‌تواند از فقدان تأیید بسته در منطق برنامه سوءاستفاده کند تا اشیاء دلخواه را تزریق کند که پس از deserialize شدن، می‌توانند برنامه را مجبور به اجرای کد مخرب کنند که ممکن است منجر به انکار سرویس (DoS)، افزایش امتیاز و اجرای کد از راه دور (RCE) شود.

این نوع حملات ممکن است نامحسوس باشند. برای مثال، یک برنامه ممکن است شامل یک intent باشد که انتظار تنها یک پارامتر را دارد که پس از اعتبارسنجی، deserialized خواهد شد. اگر یک مهاجم یک پارامتر اضافی مخرب غیرمنتظره دوم را به همراه پارامتر مورد انتظار ارسال کند، این امر باعث می‌شود که تمام اشیاء داده تزریق شده deserialized شوند زیرا intent با موارد اضافی به عنوان یک Bundle رفتار می‌کند. یک کاربر مخرب ممکن است از این رفتار برای تزریق داده‌های شیء استفاده کند که پس از deserialized شدن، ممکن است منجر به RCE، به خطر افتادن داده‌ها یا از دست دادن آنها شود.

کاهش‌ها

به عنوان بهترین روش، فرض کنید که تمام داده‌های سریالی شده غیرقابل اعتماد و بالقوه مخرب هستند. برای اطمینان از صحت داده‌های سریالی شده، بررسی‌های تأیید را روی داده‌ها انجام دهید تا مطمئن شوید که کلاس و قالب صحیحی دارند که مورد انتظار برنامه است.

یک راه حل عملی می‌تواند پیاده‌سازی الگوی look-ahead برای کتابخانه java.io.ObjectInputStream باشد. با تغییر کدی که مسئول deserialization است، می‌توانید مطمئن شوید که فقط مجموعه‌ای از کلاس‌ها که به صراحت مشخص شده‌اند، درون intent deserialized می‌شوند.

از اندروید ۱۳ (سطح API ۳۳)، چندین متد در کلاس Intent به‌روزرسانی شده‌اند که جایگزین‌های امن‌تری برای متدهای قدیمی‌تر و منسوخ‌شده برای مدیریت بسته‌ها محسوب می‌شوند. این متدهای جدیدِ نوع-ایمن‌تر، مانند getParcelableExtra(java.lang.String, java.lang.Class) و getParcelableArrayListExtra(java.lang.String, java.lang.Class) بررسی‌های نوع داده را انجام می‌دهند تا نقاط ضعف عدم تطابق را که ممکن است باعث خرابی برنامه‌ها شوند و به‌طور بالقوه برای انجام حملات افزایش امتیاز، مانند CVE-2021-0928 ، مورد سوءاستفاده قرار گیرند، شناسایی کنند.

مثال زیر نشان می‌دهد که چگونه می‌توان یک نسخه امن از کلاس Parcel را پیاده‌سازی کرد:

فرض کنید کلاس UserParcelable Parcelable پیاده‌سازی کرده و نمونه‌ای از داده‌های کاربر ایجاد می‌کند که سپس در یک Parcel نوشته می‌شود. سپس می‌توان از متد readParcelable که از نظر نوع ایمن‌تر است، برای خواندن بسته سریالی شده استفاده کرد:

کاتلین

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

جاوا

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

به مثال جاوا بالا که از UserParcelable.CREATOR درون متد استفاده می‌کند، توجه کنید. این پارامتر الزامی به متد readParcelable می‌گوید که چه نوعی را باید انتظار داشته باشد و نسبت به نسخه منسوخ‌شده متد readParcelable عملکرد بهتری دارد.

خطرات خاص

این بخش، ریسک‌هایی را جمع‌آوری می‌کند که نیاز به استراتژی‌های کاهش غیراستاندارد دارند یا در سطح خاصی از SDK کاهش یافته‌اند و برای تکمیل مطلب در اینجا آورده شده‌اند.

خطر: از رده خارج کردن ناخواسته اشیاء

پیاده‌سازی رابط Serializable درون یک کلاس، به‌طور خودکار باعث می‌شود که تمام زیرنوع‌های کلاس داده‌شده، رابط را پیاده‌سازی کنند. در این سناریو، برخی از اشیاء ممکن است رابط مذکور را به ارث ببرند، به این معنی که اشیاء خاصی که قرار نیست deserialized شوند، همچنان پردازش می‌شوند. این امر می‌تواند به‌طور ناخواسته سطح حمله را افزایش دهد.

کاهش‌ها

اگر یک کلاس از رابط Serializable ارث‌بری کند، طبق راهنمایی OWASP ، متد readObject باید به صورت زیر پیاده‌سازی شود تا از deserialize شدن مجموعه‌ای از اشیاء در کلاس جلوگیری شود:

کاتلین

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

جاوا

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

منابع