سریال‌سازی ناامن

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

نمای کلی

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

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

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

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

  • سوءاستفاده از فرض نادرست توسعه‌دهنده مبنی بر اینکه غیرسریالی کردن اشیای حاصل از نوع کلاس سفارشی ایمن است. در واقعیت، هر شیء که از هر کلاسی منبع‌یابی شده باشد می‌تواند به‌طور بالقوه با محتوای مخربی جایگزین شود که در بدترین حالت می‌تواند با بارکننده‌های کلاس همان برنامه یا برنامه‌های دیگر تداخل داشته باشد. این تداخل به‌صورت تزریق مقادیر خطرناک انجام می‌شود که بسته به هدف کلاس، ممکن است برای مثال به استخراج داده یا تصاحب حساب منجر شود.
  • سوءاستفاده از روش‌های واهم‌بندی که ازنظر طراحی ناامن تلقی می‌شوند (برای مثال CVE-2023-35669، نقص ارتقای امتیاز محلی که امکان تزریق کد جاوا اسکریپت دلخواه را ازطریق بردار واهم‌بندی پیوند عمیق فراهم می‌کرد)
  • سوءاستفاده از نقص‌های منطق برنامه (برای مثال CVE-2023-20963، نقص ارتقای امتیاز محلی که به برنامه اجازه می‌دهد کد را ازطریق نقص موجود در منطق بسته WorkSource در Android در محیطی با امتیاز بالا بارگیری و اجرا کند).

تأثیر

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

خطر: غیرسریال‌سازی ورودی غیرقابل‌اعتماد

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

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

کاهش

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

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

از Android 13 (میانای برنامه‌سازی کاربردی سطح ۳۳)، چندین روش در کلاس 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);

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

خطرات خاص

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

خطر: غیرسریال‌سازی شیء ناخواسته

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

کاهش

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

کاتلین

@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");
}

منابع