Niebezpieczny menedżer pobierania

Kategoria OWASP: MASVS-NETWORK: Network Communication

Przegląd

DownloadManager to usługa systemowa wprowadzona na poziomie interfejsu API 9. Obsługuje długotrwałe pobieranie plików przez HTTP i umożliwia aplikacjom pobieranie plików jako zadania w tle. Jego interfejs API obsługuje interakcje HTTP i ponawia pobieranie po niepowodzeniach, zmianach połączenia i ponownym uruchomieniu systemu.

Menedżer pobierania ma luki w zabezpieczeniach, które sprawiają, że nie jest bezpiecznym wyborem do zarządzania pobieraniem w aplikacjach na Androida.

(1) Luki w zabezpieczeniach w usłudze Download Provider

W 2018 roku w usłudze DownloadProvider znaleziono i załatano 3 luki w zabezpieczeniach. Poniżej znajdziesz podsumowanie każdego z nich (szczegóły techniczne znajdziesz tutaj).

  • Obejście uprawnień dostawcy pobierania – bez przyznanych uprawnień szkodliwa aplikacja może pobrać wszystkie wpisy od dostawcy pobierania, które mogą zawierać potencjalnie informacje poufne, takie jak nazwy plików, opisy, tytuły, ścieżki, adresy URL, a także pełne uprawnienia do odczytu i zapisu wszystkich pobranych plików. Złośliwa aplikacja może działać w tle, monitorując wszystkie pobierane pliki i zdalnie ujawniając ich zawartość lub modyfikując pliki na bieżąco, zanim uzyska do nich dostęp uprawniony użytkownik. Może to spowodować odmowę dostępu do usług dla użytkownika w przypadku podstawowych aplikacji, w tym brak możliwości pobierania aktualizacji.
  • Wstrzyknięcie kodu SQL na poziomie komponentu Download Provider – z powodu luki w zabezpieczeniach umożliwiającej wstrzyknięcie kodu SQL złośliwa aplikacja bez uprawnień może pobrać wszystkie wpisy z komponentu Download Provider. Aplikacje z ograniczonymi uprawnieniami, np. android.permission.INTERNET, mogą też uzyskiwać dostęp do całej zawartości bazy danych z innego identyfikatora URI. Można uzyskać dostęp do potencjalnie poufnych informacji, takich jak nazwy plików, opisy, tytuły, ścieżki i adresy URL, a w zależności od uprawnień także do pobranych treści.
  • Ujawnianie informacji o nagłówkach żądań dostawcy pobierania – złośliwa aplikacja z przyznanym uprawnieniem android.permission.INTERNET może pobrać wszystkie wpisy z tabeli nagłówków żądań dostawcy pobierania. 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 na Androida lub Google Chrome, a także w innych aplikacjach. Może to umożliwić atakującemu podszywanie się pod użytkownika na dowolnej platformie, z której uzyskano dane użytkownika wrażliwe.

(2) Niebezpieczne uprawnienia

Menedżer pobierania na poziomach interfejsu API niższych niż 29 wymaga uprawnień niebezpiecznych –android.permission.WRITE_EXTERNAL_STORAGE. W przypadku interfejsu API na poziomie 29 i wyższym android.permission.WRITE_EXTERNAL_STORAGE nie są wymagane uprawnienia, ale identyfikator URI musi odnosić się do ścieżki w katalogach należących do aplikacji lub ścieżki w katalogu najwyższego poziomu „Pobrane”.

(3) Reliance on Uri.parse()

DownloadManager korzysta z metody Uri.parse() do analizowania lokalizacji żądanego pobierania. Ze względu na wydajność klasa Uri nie przeprowadza weryfikacji niezaufanych danych wejściowych lub przeprowadza ją w niewielkim stopniu.

Wpływ

Korzystanie z DownloadManager może prowadzić do luk w zabezpieczeniach poprzez wykorzystanie uprawnień WRITE do pamięci zewnętrznej. Uprawnienia android.permission.WRITE_EXTERNAL_STORAGE zapewniają szeroki dostęp do pamięci zewnętrznej, dlatego osoba przeprowadzająca atak może po cichu modyfikować pliki i pobrane dane, instalować potencjalnie szkodliwe aplikacje, odmawiać dostępu do usług podstawowych aplikacji lub powodować ich awarie. Złośliwi użytkownicy mogą też manipulować danymi wysyłanymi do funkcji Uri.parse(), aby spowodować pobranie przez użytkownika szkodliwego pliku.

Środki ograniczające ryzyko

Zamiast korzystać z klasy DownloadManager, skonfiguruj pobieranie bezpośrednio w aplikacji za pomocą klienta HTTP (np. Cronet), harmonogramu/menedżera procesów i sposobu zapewnienia ponownych prób w przypadku utraty połączenia z siecią. Dokumentacja biblioteki zawiera link do przykładowej aplikacji oraz instrukcje dotyczące jej implementacji.

Jeśli aplikacja wymaga możliwości zarządzania harmonogramem procesów, pobierania w tle lub ponawiania pobierania po utracie połączenia z siecią, rozważ uwzględnienie WorkManagerForegroundServices.

Przykładowy kod konfiguracji pobierania za pomocą Croneta pochodzi z ćwiczeń praktycznych dotyczących Croneta:

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

Zasoby