Gerenciador de downloads não seguros

Categoria do OWASP: MASVS-NETWORK - Comunicação em rede (link em inglês)

Visão geral

O DownloadManager é um serviço do sistema introduzido no nível 9 da API. Ele processa downloads HTTP de longa duração e permite que os aplicativos façam o download de arquivos como uma tarefa em segundo plano. A API processa interações HTTP e tenta refazer os downloads após falhas ou em todas as mudanças de conectividade e reinicializações do sistema.

O DownloadManager tem pontos fracos de segurança que o tornam uma escolha não segura para gerenciar downloads em aplicativos Android.

(1) CVEs no provedor de download

Em 2018, três CVEs foram encontradas e corrigidas no provedor de downloads. Confira um resumo de cada uma delas (consulte os detalhes técnicos).

  • Bypass de permissão do provedor de download: sem permissões concedidas, um app malicioso pode extrair todas as entradas do provedor de download, o que pode incluir informações potencialmente confidenciais, como nomes de arquivos, descrições, títulos, caminhos, URLs, bem como permissões de LEITURA/GRAVAÇÃO completas para todos os arquivos baixados. Um app malicioso pode ser executado em segundo plano, monitorando todos os downloads e vazando o conteúdo remotamente ou modificando os arquivos em tempo real antes que sejam acessados pelo solicitante legítimo. Isso pode causar uma negação de serviço para o usuário em aplicativos principais, incluindo a incapacidade de fazer o download de atualizações.
  • Injeção de SQL do provedor de downloads: usando uma vulnerabilidade de injeção de SQL, um aplicativo malicioso sem permissões pode recuperar todas as entradas do provedor de downloads. Além disso, aplicativos com permissões limitadas, como android.permission.INTERNET, também podem acessar todo o conteúdo do banco de dados de um URI diferente. Informações potencialmente sensíveis, como nomes de arquivos, descrições, títulos, caminhos e URLs, podem ser recuperadas. Além disso, dependendo das permissões, o acesso ao conteúdo salvo também pode ser possível.
  • Divulgação de informações de cabeçalhos de solicitação do provedor de download: um app malicioso com a permissão android.permission.INTERNET concedida pode extrair todas as entradas da tabela de cabeçalhos de solicitação do provedor de download. Esses cabeçalhos podem incluir informações sensíveis, como cookies de sessão ou cabeçalhos de autenticação, para qualquer download iniciado no navegador Android ou no Google Chrome, entre outros apps. Isso pode permitir que um invasor se passe pelo usuário em qualquer plataforma em que dados sensíveis do usuário tenham sido obtidos.

(2) Permissões perigosas

O DownloadManager em níveis de API anteriores ao 29 exige permissões perigosas: android.permission.WRITE_EXTERNAL_STORAGE. Para o nível 29 e mais recente da API, as permissões android.permission.WRITE_EXTERNAL_STORAGE não são necessárias, mas o URI precisa se referir a um caminho dentro dos diretórios pertencentes ao aplicativo ou a um caminho dentro do diretório "Downloads" de nível superior.

(3) Dependência de Uri.parse()

O DownloadManager depende do método Uri.parse() para analisar a localização do download solicitado. Para melhorar a performance, a classe Uri aplica pouco ou nenhum tipo de validação a entradas não confiáveis.

Impacto

O uso do DownloadManager pode levar a vulnerabilidades pela exploração de permissões de ESCRITA no armazenamento externo. Como as permissões android.permission.WRITE_EXTERNAL_STORAGE permitem amplo acesso ao armazenamento externo, é possível que um invasor modifique arquivos e downloads em segundo plano, instale apps potencialmente maliciosos, negue serviço a apps principais ou cause falhas em apps. Atores maliciosos também podem manipular o que é enviado para Uri.parse() para fazer com que o usuário faça o download de um arquivo nocivo.

Mitigações

Em vez de usar o DownloadManager, configure os downloads diretamente no app usando um cliente HTTP (como o Cronet), um programador/gerenciador de processos e uma maneira de garantir tentativas se houver perda de rede. A documentação da biblioteca inclui um link para um app exemplo e instruções sobre como implementá-lo.

Se o aplicativo precisar gerenciar a programação de processos, executar downloads em segundo plano ou tentar estabelecer o download após a perda de conexão, considere incluir WorkManager e ForegroundServices.

O código de exemplo para configurar um download usando a Cronet é o seguinte, extraído do codelab da 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();
    });
}

Recursos