Gestione dei download non sicura

Categoria OWASP: MASVS-NETWORK: Network Communication

Panoramica

DownloadManager è un servizio di sistema introdotto nel livello API 9. Gestisce i download HTTP a lunga esecuzione e consente alle applicazioni di scaricare file come attività in background. La sua API gestisce le interazioni HTTP e riprova i download dopo errori o in caso di modifiche alla connettività e riavvii del sistema.

DownloadManager presenta punti deboli rilevanti per la sicurezza che lo rendono una scelta non sicura per la gestione dei download nelle applicazioni per Android.

(1) CVE nel fornitore di download

Nel 2018, sono state trovate e corrette tre CVE in Download Provider. Di seguito è riportato un riepilogo di ciascuna (vedi dettagli tecnici).

  • Elusione dell'autorizzazione del provider di download: senza autorizzazioni concesse, un'app dannosa potrebbe recuperare tutte le voci dal provider di download, che potrebbero includere informazioni potenzialmente sensibili come nomi, descrizioni, titoli, percorsi, URL, nonché autorizzazioni di LETTURA/SCRITTURA complete per tutti i file scaricati. Un'app dannosa potrebbe essere eseguita in background, monitorando tutti i download e divulgando i relativi contenuti da remoto o modificando i file al volo prima che vengano aperti dal richiedente legittimo. Ciò potrebbe causare un attacco denial of service per l'utente per le applicazioni principali, inclusa l'impossibilità di scaricare gli aggiornamenti.
  • SQL injection del provider di download: tramite una vulnerabilità SQL injection, un'applicazione dannosa senza autorizzazioni potrebbe recuperare tutte le voci dal provider di download. Inoltre, le applicazioni con autorizzazioni limitate, ad esempio android.permission.INTERNET, potrebbero accedere anche a tutti i contenuti del database da un URI diverso. Potrebbero essere recuperate informazioni potenzialmente sensibili come nomi, descrizioni, titoli, percorsi e URL dei file e, a seconda delle autorizzazioni, potrebbe essere possibile accedere anche ai contenuti scaricati.
  • Divulgazione di informazioni sulle intestazioni delle richieste del fornitore di download: un'applicazione dannosa con l'autorizzazione android.permission.INTERNET concessa potrebbe recuperare tutte le voci dalla tabella delle intestazioni delle richieste del fornitore di download. Queste intestazioni possono includere informazioni sensibili, come cookie di sessione o intestazioni di autenticazione, per qualsiasi download avviato da Android Browser o Google Chrome, tra le altre applicazioni. Ciò potrebbe consentire a un malintenzionato di impersonare l'utente su qualsiasi piattaforma da cui sono stati ottenuti dati utente sensibili.

(2) Autorizzazioni pericolose

DownloadManager nei livelli API inferiori a 29 richiede autorizzazioni pericolose: android.permission.WRITE_EXTERNAL_STORAGE. Per il livello API 29 e versioni successive, le autorizzazioni android.permission.WRITE_EXTERNAL_STORAGE non sono richieste, ma l'URI deve fare riferimento a un percorso all'interno delle directory di proprietà dell'applicazione o a un percorso all'interno della directory "Download" di primo livello.

(3) Reliance on Uri.parse()

DownloadManager si basa sul metodo Uri.parse() per analizzare la posizione del download richiesto. Nell'interesse delle prestazioni, la classe Uri applica una convalida minima o nulla all'input non attendibile.

Impatto

L'utilizzo di DownloadManager può portare a vulnerabilità attraverso lo sfruttamento delle autorizzazioni di scrittura nella memoria esterna. Poiché le autorizzazioni android.permission.WRITE_EXTERNAL_STORAGE consentono un ampio accesso allo spazio di archiviazione esterno, un malintenzionato può modificare silenziosamente file e download, installare app potenzialmente dannose, negare il servizio alle app principali o causare l'arresto anomalo delle app. Gli autori di attacchi potrebbero anche manipolare ciò che viene inviato a Uri.parse() per indurre l'utente a scaricare un file dannoso.

Mitigazioni

Anziché utilizzare DownloadManager, configura i download direttamente nella tua app utilizzando un client HTTP (ad esempio Cronet), un pianificatore/gestore di processi e un modo per garantire i tentativi in caso di perdita di rete. La documentazione della libreria include un link a un'app di esempio e istruzioni su come implementarla.

Se la tua applicazione richiede la possibilità di gestire la pianificazione dei processi, eseguire i download in background o riprovare a stabilire il download dopo la perdita della rete, valuta la possibilità di includere WorkManager e ForegroundServices.

Il seguente codice di esempio per configurare un download utilizzando Cronet è tratto dal codelab di 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();
    });
}

Risorse