Güvenli Olmayan İndirme Yöneticisi

OWASP kategorisi: MASVS-NETWORK: Ağ İletişimi

Genel Bakış

DownloadManager, API düzeyi 9'da kullanıma sunulan bir sistem hizmetidir. Uzun süren HTTP indirme işlemlerini yönetir ve uygulamaların arka plan görevi olarak dosya indirmesine olanak tanır. API'si, HTTP etkileşimlerini yönetir ve bağlantı değişiklikleri ile sistem yeniden başlatmaları sonrasında veya başarısız olduktan sonra indirmeleri yeniden dener.

DownloadManager, Android uygulamalarında indirmeleri yönetmek için güvenli olmayan bir seçim haline getiren güvenlikle ilgili zayıflıklara sahiptir.

(1) İndirme Sağlayıcısı'nda CVE'ler

2018'de İndirme Sağlayıcı'da üç CVE tespit edilerek düzeltildi. Bunların özetini aşağıda bulabilirsiniz (teknik ayrıntılara bakın).

  • İndirme Sağlayıcı İzni Atlama: Kötü amaçlı bir uygulama, izin verilmeden İndirme Sağlayıcı'dan tüm girişleri alabilir. Bu girişler arasında dosya adları, açıklamalar, başlıklar, yollar, URL'ler gibi hassas olabilecek bilgiler ve indirilen tüm dosyalar için tam OKUMA/YAZMA izinleri bulunabilir. Kötü amaçlı bir uygulama arka planda çalışabilir, tüm indirilenleri izleyebilir ve içeriklerini uzaktan sızdırabilir veya meşru talep sahibi tarafından erişilmeden önce dosyaları anında değiştirebilir. Bu durum, kullanıcının temel uygulamalarda hizmet reddine (ör. güncellemeleri indirememesine) neden olabilir.
  • İndirilen İçerik Sağlayıcısı SQL Yerleştirme: İzinleri olmayan kötü amaçlı bir uygulama, SQL yerleştirme güvenlik açığı aracılığıyla İndirilen İçerik Sağlayıcısı'ndaki tüm girişleri alabilir. Ayrıca, android.permission.INTERNET gibi sınırlı izinlere sahip uygulamalar da farklı bir URI'den tüm veritabanı içeriklerine erişebilir. Dosya adları, açıklamalar, başlıklar, yollar, URL'ler gibi hassas olabilecek bilgiler alınabilir ve izinlere bağlı olarak indirilen içeriklere de erişilebilir.
  • İndirme Sağlayıcı İstek Başlıkları Bilgi Açıklaması: android.permission.INTERNET iznine sahip kötü amaçlı bir uygulama, İndirme Sağlayıcısı istek başlıkları tablosundaki tüm girişleri alabilir. Bu üstbilgiler, diğer uygulamalar arasında Android Tarayıcı veya Google Chrome'dan başlatılan tüm indirmeler için oturum çerezleri ya da kimlik doğrulama üstbilgileri gibi hassas bilgiler içerebilir. Bu, saldırganların hassas kullanıcı verilerinin elde edildiği herhangi bir platformda kullanıcının kimliğine bürünmesine olanak tanıyabilir.

(2) Tehlikeli İzinler

API düzeyi 29'dan düşük sürümlerde DownloadManager için tehlikeli izinler (android.permission.WRITE_EXTERNAL_STORAGE) gerekir. API düzeyi 29 ve sonraki sürümler için android.permission.WRITE_EXTERNAL_STORAGE izinleri gerekli değildir ancak URI, uygulamaya ait dizinlerdeki veya üst düzey "İndirilenler" dizinindeki bir yolu referans almalıdır.

(3) Uri.parse() hizmetine güvenme

İndirme Yöneticisi, istenen indirme işleminin konumunu ayrıştırmak için Uri.parse() yöntemini kullanır. Performans açısından Uri sınıfı, güvenilmeyen girişlerde çok az veya hiç doğrulama uygulamaz.

Etki

DownloadManager'ın kullanılması, harici depolama cihazına WRITE izinlerinin kötüye kullanılmasıyla güvenlik açıklarına neden olabilir. android.permission.WRITE_EXTERNAL_STORAGE izinleri harici depolamaya geniş erişim izni verdiğinden, saldırganların dosyaları ve indirilenleri gizlice değiştirmesi, kötü amaçlı olabilecek uygulamalar yüklemesi, temel uygulamalara hizmet vermeyi reddetmesi veya uygulamaların kilitlenmesine neden olması mümkündür. Kötü amaçlı kullanıcılar, kullanıcının zararlı bir dosya indirmesine neden olmak için Uri.parse() işlevine gönderilen verileri de değiştirebilir.

Çözümler

DownloadManager yerine, bir HTTP istemcisi (Cronet gibi), bir işlem planlayıcı/yöneticisi ve ağ bağlantısı kesilirse yeniden denemelerin yapılmasını sağlayacak bir yöntem kullanarak indirmeleri doğrudan uygulamanızda ayarlayın. Kitaplığın dokümanları, örnek uygulamanın bağlantısını ve uygulamanın nasıl uygulanacağına dair talimatları içerir.

Uygulamanız; işlem planlamayı yönetme, indirme işlemlerini arka planda çalıştırma veya ağ kaybından sonra indirme işlemini yeniden yapmayı yeniden deneme özelliklerini gerektiriyorsa WorkManager ve ForegroundServices özelliklerini eklemeyi düşünebilirsiniz.

Cronet'i kullanarak indirme işlemi ayarlamak için örnek kod aşağıda verilmiştir. Bu kod, Cronet codelab'den alınmıştır.

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();
    });
}

Kaynaklar