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 sisteme ait hizmettir. Uzun süren HTTP indirmelerini işler ve uygulamaların dosyaları arka plan görevi olarak indirmesine olanak tanır. API'si, HTTP etkileşimlerini yönetir ve indirme işlemlerini hatalardan sonra veya bağlantı değişiklikleri ve sistem yeniden başlatmaları sırasında yeniden dener.

DownloadManager, Android uygulamalarındaki indirmeleri yönetmek için güvenli olmayan bir seçenek haline getiren, güvenlikle ilgili zayıf noktalara sahiptir.

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

2018'de DownloadProvider'da üç CVE bulundu ve düzeltildi. Her birinin özeti aşağıda verilmiştir (teknik ayrıntılar için ilgili bölüme bakın).

  • İndirme Sağlayıcı İzni Atlama: İzin verilmeyen kötü amaçlı bir uygulama, İndirme Sağlayıcı'daki tüm girişleri alabilir. Bu girişler, dosya adları, açıklamalar, başlıklar, yollar, URL'ler gibi hassas olabilecek bilgilerin yanı sıra indirilen tüm dosyalar için tam OKUMA/YAZMA izinlerini içerebilir. Kötü amaçlı bir uygulama arka planda çalışarak tüm indirmeleri izleyebilir ve içeriklerini uzaktan sızdırabilir veya dosyaları, meşru istekte bulunan kişi erişmeden önce anında değiştirebilir. Bu durum, kullanıcının temel uygulamalarda hizmet reddiyle karşılaşmasına (ör. güncellemeleri indirememesine) neden olabilir.
  • Download Provider SQL Yerleştirme: SQL yerleştirme güvenlik açığı sayesinde, izinleri olmayan kötü amaçlı bir uygulama, Download Provider'daki 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 bilgiler alınabilir ve izinlere bağlı olarak indirilen içeriklere erişim de mümkün olabilir.
  • Download Provider Request Headers Information Disclosure: android.permission.INTERNET izni verilmiş kötü amaçlı bir uygulama, Download Provider istek başlıkları tablosundaki tüm girişleri alabilir. Bu başlıklar, diğer uygulamaların yanı sıra Android Tarayıcı veya Google Chrome'dan başlatılan tüm indirmeler için oturum çerezleri ya da kimlik doğrulama başlıkları gibi hassas bilgiler içerebilir. Bu durum, saldırganı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 olan sürümlerdeki DownloadManager, tehlikeli izinler gerektirir: android.permission.WRITE_EXTERNAL_STORAGE. API düzeyi 29 ve sonraki sürümlerde android.permission.WRITE_EXTERNAL_STORAGE izinleri gerekli değildir ancak URI, uygulamanın sahip olduğu dizinlerdeki bir yolu veya üst düzey "Downloads" dizinindeki bir yolu referans vermelidir.

(3) Uri.parse() güvenme

DownloadManager, istenen indirmenin 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 doğrulama uygular veya hiç doğrulama uygulamaz.

Etki

DownloadManager'ın kullanılması, harici depolama alanına YAZMA izinlerinin kötüye kullanılması yoluyla güvenlik açıklarına yol açabilir. android.permission.WRITE_EXTERNAL_STORAGE izinleri, harici depolamaya geniş kapsamlı erişime izin verdiğinden saldırganlar dosyaları ve indirmeleri sessizce değiştirebilir, kötü amaçlı olabilecek uygulamaları yükleyebilir, temel uygulamalara hizmet erişimini engelleyebilir veya uygulamaların kilitlenmesine neden olabilir. Kötü niyetli kişiler, kullanıcının zararlı bir dosya indirmesine neden olmak için Uri.parse() işlevine gönderilenleri de manipüle edebilir.

Çözümler

DownloadManager'ı kullanmak yerine, HTTP istemcisi (ör. Cronet), işlem planlayıcı/yönetici ve ağ kaybı olması durumunda yeniden denemeleri sağlayacak bir yöntem kullanarak indirmeleri doğrudan uygulamanızda ayarlayın. Kitaplığın dokümanlarında, örnek bir uygulamanın bağlantısının yanı sıra nasıl uygulanacağına dair talimatlar da yer alır.

Uygulamanızın işlem planlamasını yönetme, indirme işlemlerini arka planda çalıştırma veya ağ bağlantısı kaybolduktan sonra indirme işlemini yeniden başlatma gibi özelliklere ihtiyacı varsa WorkManager ve ForegroundServices izinlerini eklemeyi düşünebilirsiniz.

Cronet kullanarak indirme ayarlamaya ilişkin örnek kod, Cronet codelab'inden alınmıştır ve aşağıda verilmiştir.

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