קטגוריית OWASP: MASVS-NETWORK: Network Communication
סקירה כללית
DownloadManager הוא שירות מערכת שהוצג ברמת API 9. הוא מטפל בהורדות ארוכות של HTTP ומאפשר לאפליקציות להוריד קבצים כמשימה ברקע. ה-API שלו מטפל באינטראקציות של HTTP ומנסה שוב להוריד אחרי כשלים או אחרי שינויים בקישוריות ואתחול מחדש של המערכת.
ל-DownloadManager יש חולשות שקשורות לאבטחה, ולכן הוא לא בחירה מאובטחת לניהול הורדות באפליקציות ל-Android.
(1) CVEs in Download Provider
בשנת 2018, נמצאו שלושה CVE ותוקנו ב-Download Provider. בהמשך מופיע סיכום של כל אחת מהשיטות (פרטים טכניים).
- עקיפת הרשאת ספק ההורדות – ללא הרשאות שניתנו, אפליקציה זדונית יכולה לאחזר את כל הרשומות מספק ההורדות, שיכולות לכלול מידע רגיש כמו שמות קבצים, תיאורים, כותרות, נתיבים, כתובות URL, וגם הרשאות קריאה/כתיבה מלאות לכל הקבצים שהורדו. אפליקציה זדונית יכולה לפעול ברקע, לעקוב אחרי כל ההורדות ולחשוף את התוכן שלהן מרחוק, או לשנות את הקבצים תוך כדי תנועה לפני שהמבקש הלגיטימי ניגש אליהם. הדבר עלול לגרום למשתמשים למניעת שירות באפליקציות ליבה, כולל חוסר אפשרות להוריד עדכונים.
- הזרקת SQL ב-Download Provider – באמצעות פרצת אבטחה של הזרקת SQL, אפליקציה זדונית ללא הרשאות יכולה לאחזר את כל הרשומות מ-Download Provider. בנוסף, אפליקציות עם הרשאות מוגבלות, כמו
android.permission.INTERNET, יכולות גם לגשת לכל התוכן במסד הנתונים מ-URI אחר. יכול להיות שיישלף מידע רגיש כמו שמות קבצים, תיאורים, כותרות, נתיבים וכתובות URL, ובהתאם להרשאות, יכול להיות שגם תהיה גישה לתוכן שהורד. - חשיפת מידע בכותרות הבקשה של ספק ההורדות – אפליקציה זדונית עם הרשאה
android.permission.INTERNETיכולה לאחזר את כל הרשומות מטבלת כותרות הבקשה של ספק ההורדות. הכותרות האלה עשויות לכלול מידע רגיש, כמו קובצי Cookie של סשן או כותרות אימות, לכל הורדה שהתחילה מדפדפן Android או מ-Google Chrome, בין היתר. התוקף יוכל להתחזות למשתמש בכל פלטפורמה שממנה הושגו נתונים רגישים של המשתמש.
(2) הרשאות מסוכנות
DownloadManager ברמות API נמוכות מ-29 דורש הרשאות מסוכנות –
android.permission.WRITE_EXTERNAL_STORAGE. ברמת API 29 ומעלה, לא נדרשות הרשאות android.permission.WRITE_EXTERNAL_STORAGE, אבל ה-URI חייב להתייחס לנתיב בתוך הספריות שבבעלות האפליקציה או לנתיב בתוך ספריית 'הורדות' ברמה העליונה.
(3) הסתמכות על Uri.parse()
DownloadManager מסתמך על השיטה Uri.parse() כדי לנתח את המיקום של ההורדה המבוקשת. לצורך שיפור הביצועים, המחלקה Uri לא מבצעת אימות של קלט לא מהימן, או מבצעת אימות מועט בלבד.
השפעה
שימוש ב-DownloadManager עלול להוביל לפגיעויות באמצעות ניצול של הרשאות WRITE לאחסון חיצוני. ההרשאות android.permission.WRITE_EXTERNAL_STORAGE מאפשרות גישה רחבה לאחסון חיצוני, ולכן תוקף יכול לשנות קבצים והורדות באופן שקט, להתקין אפליקציות זדוניות פוטנציאליות, לגרום להכחשת שירות באפליקציות ליבה או לגרום לקריסת אפליקציות. גורמים זדוניים יכולים גם לתמרן את מה שנשלח אל Uri.parse() כדי לגרום למשתמש להוריד קובץ מזיק.
אמצעי צמצום סיכונים
במקום להשתמש ב-DownloadManager, צריך להגדיר הורדות ישירות באפליקציה באמצעות לקוח HTTP (כמו Cronet), מתזמן או מנהל תהליכים ודרך להבטיח ניסיונות חוזרים אם יש אובדן נתונים ברשת. התיעוד של הספרייה כולל קישור לאפליקציה לדוגמה והוראות להטמעה שלה.
אם האפליקציה שלכם צריכה לנהל את תזמון התהליכים, להריץ הורדות ברקע או לנסות שוב להוריד אחרי ניתוק מהרשת, כדאי לכלול את WorkManager ואת ForegroundServices.
הקוד הבא הוא דוגמה להגדרת הורדה באמצעות Cronet, והוא לקוח משיעור ה-Codelab בנושא Cronet.
Kotlin
override suspend fun downloadImage(url: String): ImageDownloaderResult {
val startNanoTime = System.nanoTime()
return suspendCoroutine {
cont ->
val request = engine.newUrlRequestBuilder(url, object: ReadToMemoryCronetCallback() {
override fun onSucceeded(
request: UrlRequest,
info: UrlResponseInfo,
bodyBytes: ByteArray) {
cont.resume(ImageDownloaderResult(
successful = true,
blob = bodyBytes,
latency = Duration.ofNanos(System.nanoTime() - startNanoTime),
wasCached = info.wasCached(),
downloaderRef = this@CronetImageDownloader))
}
override fun onFailed(
request: UrlRequest,
info: UrlResponseInfo,
error: CronetException
) {
Log.w(LOGGER_TAG, "Cronet download failed!", error)
cont.resume(ImageDownloaderResult(
successful = false,
blob = ByteArray(0),
latency = Duration.ZERO,
wasCached = info.wasCached(),
downloaderRef = this@CronetImageDownloader))
}
}, executor)
request.build().start()
}
}
Java
@Override
public CompletableFuture<ImageDownloaderResult> downloadImage(String url) {
long startNanoTime = System.nanoTime();
return CompletableFuture.supplyAsync(() -> {
UrlRequest.Builder requestBuilder = engine.newUrlRequestBuilder(url, new ReadToMemoryCronetCallback() {
@Override
public void onSucceeded(UrlRequest request, UrlResponseInfo info, byte[] bodyBytes) {
return ImageDownloaderResult.builder()
.successful(true)
.blob(bodyBytes)
.latency(Duration.ofNanos(System.nanoTime() - startNanoTime))
.wasCached(info.wasCached())
.downloaderRef(CronetImageDownloader.this)
.build();
}
@Override
public void onFailed(UrlRequest request, UrlResponseInfo info, CronetException error) {
Log.w(LOGGER_TAG, "Cronet download failed!", error);
return ImageDownloaderResult.builder()
.successful(false)
.blob(new byte[0])
.latency(Duration.ZERO)
.wasCached(info.wasCached())
.downloaderRef(CronetImageDownloader.this)
.build();
}
}, executor);
UrlRequest urlRequest = requestBuilder.build();
urlRequest.start();
return urlRequest.getResult();
});
}
משאבים
- הדף הראשי של התיעוד בנושא DownloadManager
- דוח לגבי פגיעויות ב-DownloadManager
- Android Permission Bypass CVE 2018-9468
- הזרקת SQL ב-Android Download Provider CVE-2018-9493
- עקיפת הרשאת ספק ההורדות ב-Android CVE2018-9468
- הדף הראשי של התיעוד בנושא Cronet
- הוראות לשימוש ב-Cronet באפליקציה
- דוגמה להטמעה של Cronet
- תיעוד עבור Uri
- תיעוד בנושא ForegroundService
- תיעוד של WorkManager