קטגוריה ב-OWASP: MASVS-PLATFORM: Platform Interaction
סקירה כללית
לפי המסמכים, ContentResolver
הוא 'כיתה שמספקת לאפליקציות גישה למודל התוכן'. פותרי תוכן חושפים שיטות ליצירת אינטראקציה עם תוכן, לאחזור תוכן או לשינוי תוכן שמסופק מהמקורות הבאים:
- אפליקציות מותקנות (סכימת URI של
content://
) - מערכות קבצים (סכימת URI מסוג
file://
) - תמיכה בממשקי API כפי שסופקו על ידי Android (סכימת URI מסוג
android.resource://
).
לסיכום, נקודות החולשה שקשורות ל-ContentResolver
שייכות לקטגוריה משרת מבולבל, כי התוקף יכול להשתמש בהרשאות של אפליקציה פגיעה כדי לגשת לתוכן מוגן.
סיכון: ניצול לרעה על סמך URI לא מהימן מסוג file://
ניצול לרעה של ContentResolver
באמצעות נקודת החולשה של ה-URI file://
מנצל את היכולת של ContentResolver
להחזיר מתארי קבצים שמתוארים על ידי ה-URI. נקודת החולשה הזו משפיעה על פונקציות כמו openFile()
, openFileDescriptor()
, openInputStream()
, openOutputStream()
ו-openAssetFileDescriptor()
מ-API של ContentResolver
. אפשר לנצל לרעה את נקודת החולשה באמצעות URI של file://
שנמצא בשליטה מלאה או חלקית של תוקף, כדי לאלץ את האפליקציה לגשת לקבצים שלא נועדו להיות נגישים, כמו מסדי נתונים פנימיים או העדפות משותפות.
אחד מתרחישי ההתקפה האפשריים הוא יצירת גלריה או בורר קבצים זדוניים, שבשימוש באפליקציה פגיעה יחזירו URI זדוני.
יש כמה וריאציות של ההתקפה הזו:
- מזהה URI מסוג
file://
שנמצא בשליטה מלאה של תוקף ומפנה לקבצים הפנימיים של אפליקציה - חלק מ-URI של
file://
נמצא בשליטת תוקפים, ולכן הוא חשוף לפריצות נתיב file://
URI שמטרגט קישור סמלי (symlink) שנמצא בשליטת תוקף ומפנה לקובצי האפליקציה הפנימיים- בדומה לגרסה הקודמת, אבל כאן התוקף מחליף שוב ושוב את יעד הקישור הלא פורמלי מיעד לגיטימי לקבצים הפנימיים של האפליקציה. המטרה היא לנצל מרוץ תהליכים בין בדיקת אבטחה פוטנציאלית לבין שימוש בנתיב הקובץ
השפעה
ההשפעה של ניצול נקודת החולשה הזו משתנה בהתאם לשימוש ב-ContentResolver. במקרים רבים, הדבר עלול לגרום לזליגת נתונים מוגנים של אפליקציה או לשינויים בנתונים מוגנים על ידי גורמים לא מורשים.
פעולות מיטיגציה
כדי לצמצם את נקודת החולשה הזו, צריך להשתמש באלגוריתם הבא כדי לאמת את מתאר הקובץ. אחרי שעוברים את תהליך האימות, אפשר להשתמש בבטחה בתיאור הקובץ.
Kotlin
fun isValidFile(ctx: Context, pfd: ParcelFileDescriptor, fileUri: Uri): Boolean {
// Canonicalize to resolve symlinks and path traversals.
val fdCanonical = File(fileUri.path!!).canonicalPath
val pfdStat: StructStat = Os.fstat(pfd.fileDescriptor)
// Lstat doesn't follow the symlink.
val canonicalFileStat: StructStat = Os.lstat(fdCanonical)
// Since we canonicalized (followed the links) the path already,
// the path shouldn't point to symlink unless it was changed in the
// meantime.
if (OsConstants.S_ISLNK(canonicalFileStat.st_mode)) {
return false
}
val sameFile =
pfdStat.st_dev == canonicalFileStat.st_dev &&
pfdStat.st_ino == canonicalFileStat.st_ino
if (!sameFile) {
return false
}
return !isBlockedPath(ctx, fdCanonical)
}
fun isBlockedPath(ctx: Context, fdCanonical: String): Boolean {
// Paths that should rarely be exposed
if (fdCanonical.startsWith("/proc/") ||
fdCanonical.startsWith("/data/misc/")) {
return true
}
// Implement logic to block desired directories. For example, specify
// the entire app data/ directory to block all access.
}
Java
boolean isValidFile(Context ctx, ParcelFileDescriptor pfd, Uri fileUri) {
// Canonicalize to resolve symlinks and path traversals
String fdCanonical = new File(fileUri.getPath()).getCanonicalPath();
StructStat pfdStat = Os.fstat(pfd.getFileDescriptor());
// Lstat doesn't follow the symlink.
StructStat canonicalFileStat = Os.lstat(fdCanonical);
// Since we canonicalized (followed the links) the path already,
// the path shouldn't point to symlink unless it was changed in the meantime
if (OsConstants.S_ISLNK(canonicalFileStat.st_mode)) {
return false;
}
boolean sameFile =
pfdStat.stDev == canonicalFileStat.stDev && pfdStat.stIno == canonicalFileStat.stIno;
if (!sameFile) {
return false;
}
return !isBlockedPath(ctx, fdCanonical);
}
boolean isBlockedPath(Context ctx, String fdCanonical) {
// Paths that should rarely be exposed
if (fdCanonical.startsWith("/proc/") || fdCanonical.startsWith("/data/misc/")) {
return true;
}
// Implement logic to block desired directories. For example, specify
// the entire app data/ directory to block all access.
}
סיכון: ניצול לרעה על סמך URI של content:// לא מהימן
ניצול לרעה של ContentResolver
באמצעות נקודת חולשה של URI ב-content://
מתרחש כאשר מזהה URI שנמצא בשליטה מלאה או חלקית של תוקף מועבר לממשקי API של ContentResolver
כדי לבצע פעולות בתוכן שלא היה אמור להיות נגיש.
יש שני תרחישים עיקריים להתקפה הזו:
- האפליקציה פועלת על תוכן פנימי משלה. לדוגמה: אחרי קבלת URI מתוקף, אפליקציית האימייל מצרפת נתונים מספק התוכן הפנימי שלה במקום תמונה חיצונית.
- האפליקציה פועלת כשרת proxy ולאחר מכן ניגשת לנתונים של אפליקציה אחרת בשביל התוקף. לדוגמה: אפליקציית האימייל מצרפת נתונים מאפליקציה X שמוגנים על ידי הרשאה שבדרך כלל לא מאפשרת לתוקף לראות את הקובץ המצורף הספציפי הזה. הוא זמין לאפליקציה שמצרפת את הקובץ, אבל לא בהתחלה, כך שהתוכן הזה לא מועבר לתוקף.
תרחיש התקפה אפשרי אחד הוא יצירת גלריה או בורר קבצים זדוניים, שבשימוש באפליקציה פגיעה יחזירו URI זדוני.
השפעה
ההשפעה של ניצול נקודת החולשה הזו משתנה בהתאם להקשר שמשויך ל-ContentResolver. כתוצאה מכך, נתונים מוגנים של אפליקציה עלולים לזלוג או לעבור שינויים על ידי גורמים לא מורשים.
פעולות מיטיגציה
כללי
אימות של מזהי URI נכנסים. לדוגמה, שימוש ברשימת ההיתרים של הרשאות צפויות נחשב לשימוש טוב.
URI מטרגט ספק תוכן שלא מיוצא או מוגן באמצעות הרשאות, וששייך לאפליקציה פגיעה
בודקים אם ה-URI מטרגט את האפליקציה:
Kotlin
fun belongsToCurrentApplication(ctx: Context, uri: Uri): Boolean {
val authority: String = uri.authority.toString()
val info: ProviderInfo =
ctx.packageManager.resolveContentProvider(authority, 0)!!
return ctx.packageName.equals(info.packageName)
}
Java
boolean belongsToCurrentApplication(Context ctx, Uri uri){
String authority = uri.getAuthority();
ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);
return ctx.getPackageName().equals(info.packageName);
}
לחלופין, אם הספק המטורגט מיוצא:
Kotlin
fun isExported(ctx: Context, uri: Uri): Boolean {
val authority = uri.authority.toString()
val info: ProviderInfo =
ctx.packageManager.resolveContentProvider(authority, 0)!!
return info.exported
}
Java
boolean isExported(Context ctx, Uri uri){
String authority = uri.getAuthority();
ProviderInfo info = ctx.getPackageManager().resolveContentProvider(authority, 0);
return info.exported;
}
או אם ניתנה הרשאה מפורשת לכתובת ה-URI – הבדיקה הזו מבוססת על ההנחה שאם ניתנה הרשאה מפורשת לגשת לנתונים, כתובת ה-URI לא זדונית:
Kotlin
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
fun wasGrantedPermission(ctx: Context, uri: Uri?, grantFlag: Int): Boolean {
val pid: Int = Process.myPid()
val uid: Int = Process.myUid()
return ctx.checkUriPermission(uri, pid, uid, grantFlag) ==
PackageManager.PERMISSION_GRANTED
}
Java
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
boolean wasGrantedPermission(Context ctx, Uri uri, int grantFlag){
int pid = Process.myPid();
int uid = Process.myUid();
return ctx.checkUriPermission(uri, pid, uid, grantFlag) == PackageManager.PERMISSION_GRANTED;
}
ה-URI מטרגט ל-ContentProvider שמוגן באמצעות הרשאות וששייך לאפליקציה אחרת שמאמינה באפליקציה הפגיעה.
ההתקפה הזו רלוונטית למקרים הבאים:
- מערכות אקולוגיות של אפליקציות שבהן האפליקציות מגדירות הרשאות בהתאמה אישית או מנגנוני אימות אחרים ומשתמשות בהן.
- התקפות proxy של הרשאות, שבהן תוקף מנצל לרעה אפליקציה פגיעה שיש לה הרשאת זמן ריצה, כמו READ_CONTACTS, כדי לאחזר נתונים מספק מערכת.
בודקים אם הרשאת ה-URI הוענקה:
Kotlin
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
fun wasGrantedPermission(ctx: Context, uri: Uri?, grantFlag: Int): Boolean {
val pid: Int = Process.myPid()
val uid: Int = Process.myUid()
return ctx.checkUriPermission(uri, pid, uid, grantFlag) ==
PackageManager.PERMISSION_GRANTED
}
Java
// grantFlag is one of: FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION
boolean wasGrantedPermission(Context ctx, Uri uri, int grantFlag){
int pid = Process.myPid();
int uid = Process.myUid();
return ctx.checkUriPermission(uri, pid, uid, grantFlag) == PackageManager.PERMISSION_GRANTED;
}
אם השימוש בספקי תוכן אחרים לא מחייב מתן הרשאה – למשל, אם האפליקציה מאפשרת לכל האפליקציות מהסביבה העסקית לגשת לכל הנתונים – צריך לאסור במפורש את השימוש ברשויות האלה.