קטגוריה של OWASP: MASVS-CODE: איכות הקוד
סקירה כללית
כשמאחסנים או מעבירים כמויות גדולות של נתוני אובייקטים ב-Java, לרוב יעיל יותר לבצע סריאליזציה של הנתונים קודם. לאחר מכן הנתונים יעברו תהליך של ביטול סדרת נתונים על ידי האפליקציה, הפעילות או הספק המקבלים, שבסופו של דבר יטפלו בנתונים. בנסיבות רגילות, הנתונים עוברים סריאליזציה ואז דה-סריאליזציה ללא התערבות של המשתמש. עם זאת, אפשר לנצל לרעה את יחסי האמון בין תהליך הביטול של הסריאליזציה לבין האובייקט המיועד שלו. למשל, גורם זדוני יכול ליירט ולשנות אובייקטים שעברו סריאליזציה. הדבר יאפשר לגורם הזדוני לבצע מתקפות כמו מניעת שירות (DoS), הסלמת הרשאות (privilege escalation) והרצת קוד מרחוק (RCE).
המחלקות Serializable הן שיטה נפוצה לניהול סריאליזציה, אבל ל-Android יש מחלקה משלה לטיפול בסריאליזציה שנקראת Parcel. באמצעות המחלקה Parcel, אפשר לבצע סריאליזציה של נתוני אובייקט לנתוני זרם בייטים ולארוז אותם ב-Parcel באמצעות הממשק Parcelable.
כך אפשר להעביר או לאחסן את Parcel בצורה יעילה יותר.
עם זאת, צריך לשקול היטב את השימוש במחלקה Parcel, כי היא מיועדת להיות מנגנון יעיל מאוד להעברת נתונים בין תהליכים (IPC), אבל לא מומלץ להשתמש בה לאחסון אובייקטים שעברו סריאליזציה באחסון המקומי הקבוע, כי זה עלול להוביל לבעיות תאימות או לאובדן נתונים. כשצריך לקרוא את הנתונים, אפשר להשתמש בממשק Parcelable כדי לבצע דה-סריאליזציה של Parcel ולהפוך אותו בחזרה לנתוני אובייקט.
יש שלושה וקטורים עיקריים לניצול של ביטול סדרות ב-Android:
- ניצול הנחה שגויה של מפתח שלפיה פירוק של אובייקטים מסוג מחלקה מותאמת אישית הוא בטוח. בפועל, כל אובייקט שמקורו בכל מחלקה עלול להיות מוחלף בתוכן זדוני, ובמקרה הגרוע ביותר, יכול לשבש את טועני המחלקות של אותה אפליקציה או של אפליקציות אחרות. ההפרעה הזו מתבטאת בהחדרת ערכים מסוכנים, שלפי מטרת המחלקה, עלולים להוביל, למשל, להוצאת נתונים או להשתלטות על חשבון.
- ניצול שיטות ביטול סדרות שנחשבות לא בטוחות לפי העיצוב (לדוגמה, CVE-2023-35669, פגם בהעלאת הרשאות מקומיות שאיפשר הזרקת קוד JavaScript אקראי דרך וקטור ביטול סדרות של קישור עמוק)
- ניצול חולשות בלוגיקה של האפליקציה (לדוגמה, CVE-2023-20963, חולשה מקומית בהרשאות שמאפשרת לאפליקציה להוריד ולהפעיל קוד בסביבה עם הרשאות מיוחדות דרך חולשה בלוגיקה של חבילת WorkSource ב-Android).
השפעה
כל אפליקציה שמבצעת דה-סריאליזציה של נתונים סריאליים לא מהימנים או זדוניים עלולה להיות פגיעה להרצת קוד מרחוק או להתקפות מניעת שירות.
סיכון: ביטול הסריאליזציה של קלט לא מהימן
התוקף יכול לנצל את העובדה שאין אימות של חבילות בתוך הלוגיקה של האפליקציה כדי להחדיר אובייקטים שרירותיים, שאחרי הסרת הסדרות שלהם יכולים לחייב את האפליקציה להריץ קוד זדוני שעלול לגרום למניעת שירות (DoS), להסלמת הרשאות ולביצוע קוד מרחוק (RCE).
יכול להיות שהתקיפות האלה לא יהיו בולטות. לדוגמה, אפליקציה יכולה להכיל כוונה (intent) שמצפה לפרמטר אחד בלבד, שאחרי האימות שלו יבוטל הסדר שלו. אם תוקף ישלח פרמטר נוסף זדוני ולא צפוי יחד עם הפרמטר הצפוי, כל אובייקטי הנתונים שיוחדרו יעברו דה-סריאליזציה, כי הכוונה היא לטפל בפרמטרים הנוספים כBundle. משתמש זדוני עלול לנצל את ההתנהגות הזו כדי להחדיר נתוני אובייקט
שלאחר ביטול הסדרתיות שלהם עלולים להוביל להרצת קוד מרחוק (RCE), לפגיעה בנתונים או לאובדן נתונים.
אמצעי צמצום סיכונים
כשיטה מומלצת, כדאי להניח שכל הנתונים שעברו סריאליזציה הם לא מהימנים ועלולים להיות זדוניים. כדי להבטיח את התקינות של נתונים שעברו סריאליזציה, צריך לבצע בדיקות אימות של הנתונים כדי לוודא שהם מהסוג הנכון ובפורמט שצפוי באפליקציה.
פתרון אפשרי הוא להטמיע את התבנית look-ahead עבור java.io.ObjectInputStream הספרייה. אפשר לשנות את הקוד שאחראי על ביטול הסריאליזציה כדי לוודא שרק קבוצה של מחלקות שצוינה במפורש תעבור ביטול סריאליזציה בתוך הכוונה.
החל מ-Android 13 (רמת API 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, צריך להטמיע את ה-method 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");
}
משאבים
- Parcelables
- מגרש
- Serializable
- הרציונל
- פגיעויות בביטול הסדרות ב-Android: היסטוריה קצרה
- Android Parcels: The Bad, the Good and the Better (סרטון)
- Android Parcels: The Bad, the Good and the Better (presentation slides)
- CVE-2014-7911: Android <5.0 Privilege Escalation using ObjectInputStream
- CVE-CVE-2017-0412
- CVE-2021-0928: אי התאמה בין סריאליזציה לביטול סריאליזציה של Parcel
- ההנחיות של OWASP