Niebezpieczny menedżer pobierania

Kategoria OWASP: MASVS-NETWORK: komunikacja sieciowa

Omówienie

DownloadManager to usługa systemowa wprowadzona w interfejsie API poziomu 9. Obsługuje długotrwałe pobieranie przez HTTP i pozwala aplikacjom pobierać pliki jako zadania w tle. Jego interfejs API obsługuje interakcje HTTP i ponawia próby pobrania w przypadku awarii, zmian połączenia i ponownego uruchomienia systemu.

DownloadManager ma luki w zabezpieczeniach związane z bezpieczeństwem, przez co nie jest to bezpieczne rozwiązanie do zarządzania pobieraniem w aplikacjach na Androida.

(1) CVE w dostawcy pobierania

W 2018 r. w DownloadProvider znaleziono 3 luki CVE, które zostały załatane. Poniżej znajdziesz podsumowanie każdego z nich (patrz szczegóły techniczne).

  • Pominięcie uprawnień dostawcy – bez przyznanych uprawnień złośliwa aplikacja może pobrać wszystkie wpisy od dostawcy pobierania, które mogą zawierać potencjalnie poufne informacje, takie jak nazwy plików, opisy, tytuły, ścieżki, adresy URL, a także pełne uprawnienia do ODCZYTU/ZAPISU wszystkich pobranych plików. Złośliwa aplikacja może działać w tle, monitorując wszystkie pliki do pobrania i zdalnie udostępniając ich zawartość lub modyfikując je na bieżąco, zanim uzyska do nich dostęp żądający użytkownik. Może to spowodować odmowę usługi dla użytkownika w przypadku podstawowych aplikacji, w tym brak możliwości pobrania aktualizacji.
  • Wstrzyknięcie kodu SQL na poziomie dostawcy pobierania – dzięki luki w zabezpieczeniach umożliwiającej wstrzyknięcie kodu SQL złośliwa aplikacja bez uprawnień może pobrać wszystkie wpisy od dostawcy pobierania. Aplikacje z ograniczonymi uprawnieniami, np. android.permission.INTERNET, mogą też uzyskiwać dostęp do wszystkich treści bazy danych za pomocą innego identyfikatora URI. Mogą też zostać pobrane informacje poufne, takie jak nazwy plików, opisy, tytuły, ścieżki i adresy URL. W zależności od uprawnień może być również możliwy dostęp do pobranych treści.
  • Ujawnienie informacji z nagłówków żądań dostawcy treści do pobrania – złośliwa aplikacja z przyznanym uprawnieniem android.permission.INTERNET może pobrać wszystkie wpisy z tabeli nagłówków żądań dostawcy treści do pobrania. Te nagłówki mogą zawierać informacje poufne, takie jak pliki cookie sesji lub nagłówki uwierzytelniania, w przypadku każdego pobierania rozpoczętego w przeglądarce Androida lub przeglądarce Google Chrome. Może to umożliwić atakującemu podszywanie się pod użytkownika na dowolnej platformie, z której pozyskano dane wrażliwe.

(2) Niebezpieczne uprawnienia

Interfejs DownloadManager na poziomie niższym niż 29 wymaga uprawnień niebezpiecznych –android.permission.WRITE_EXTERNAL_STORAGE. W przypadku interfejsu API na poziomie 29 lub wyższym uprawnienia android.permission.WRITE_EXTERNAL_STORAGE nie są wymagane, ale identyfikator URI musi wskazywać ścieżkę w katalogach należących do aplikacji lub ścieżkę w katalogu najwyższego poziomu „Pobrane”.

(3) Uzależnienie od Uri.parse()

Menedżer pobierania korzysta z metody Uri.parse(), aby analizować lokalizację żądanego pliku. Ze względu na wydajność klasa Uri stosuje niewiele lub wcale nie stosuje weryfikacji w przypadku niesprawdzonych danych wejściowych.

Wpływ

Korzystanie z klasy DownloadManager może prowadzić do luk w zabezpieczeniach, ponieważ umożliwia wykorzystywanie uprawnień do zapisu w pamięci zewnętrznej. Uprawnienia android.permission.WRITE_EXTERNAL_STORAGE dają szeroki dostęp do pamięci zewnętrznej, dlatego atakujący może dyskretnie modyfikować i pobierać pliki, instalować potencjalnie szkodliwe aplikacje, odrzucać usługi oraz powodować awarie aplikacji. Osoby o złośliwych zamiarach mogą też manipulować tym, co jest wysyłane do funkcji Uri.parse(), aby zmusić użytkownika do pobrania szkodliwego pliku.

Środki zaradcze

Zamiast używać DownloadManager, skonfiguruj pobieranie bezpośrednio w aplikacji, korzystając z klienta HTTP (np. Cronet), harmonogramu procesów lub menedżera procesów oraz sposobu zapewnienia ponownych prób w przypadku utraty połączenia z internetem. Dokumentacja biblioteki zawiera link do przykładowej aplikacji oraz instrukcje dotyczące jej implementacji.

Jeśli Twoja aplikacja wymaga możliwości zarządzania harmonogramem procesów, uruchamiania pobierania w tle lub ponownego nawiązywania połączenia po utracie połączenia z internetem, rozważ dodanie WorkManagerForegroundServices.

Oto przykładowy kod konfiguracji pobierania za pomocą Cronet (wycięty w 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();
    });
}

Materiały