Categoria do OWASP: MASVS-NETWORK - Comunicação em rede
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 baixem arquivos como uma tarefa em segundo plano. A API dele 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 relevantes para a segurança que o tornam uma opção insegura para gerenciar downloads em aplicativos Android.
(1) CVEs no provedor de downloads
Em 2018, três CVEs foram encontradas e corrigidas no provedor de downloads. Confira um resumo de cada uma delas (consulte detalhes técnicos).
- Bypass de permissão do provedor de downloads : sem permissões concedidas, um app malicioso pode recuperar todas as entradas do provedor de downloads, que podem incluir informações sensíveis, 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 todas as transferências e vazando o conteúdo delas remotamente ou modificando os arquivos em tempo real antes que eles 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 baixar atualizações.
- Injeção de SQL do provedor de downloads : por meio de 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 e, dependendo das permissões, o acesso ao conteúdo baixado também pode ser possível. - Divulgação de informações de cabeçalhos de solicitação do provedor de downloads : um aplicativo malicioso
com a permissão
android.permission.INTERNETconcedida pode recuperar todas as entradas da tabela de cabeçalhos de solicitação do provedor de downloads. 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 aplicativos. Isso pode permitir que um invasor se passe pelo usuário em qualquer plataforma em que dados do usuário sensíveis foram obtidos.
(2) Permissões perigosas
O DownloadManager em níveis de API mais baixos que 29 exige permissões perigosas –
android.permission.WRITE_EXTERNAL_STORAGE. Para o nível 29 da API
e versões mais recentes, android.permission.WRITE_EXTERNAL_STORAGE
permissões não são necessárias, mas o URI precisa se referir a um caminho nos
diretórios de propriedade do aplicativo ou a um caminho no diretório "Downloads"
de nível superior.
(3) Dependência de Uri.parse()
O DownloadManager depende do método Uri.parse() para analisar o local do download solicitado. Para melhorar o desempenho, a classe Uri aplica pouca ou nenhuma validação em entradas não confiáveis.
Impacto
O uso do DownloadManager pode levar a vulnerabilidades pela exploração de permissões de GRAVAÇÃO no armazenamento externo. Como as permissões android.permission.WRITE_EXTERNAL_STORAGE permitem acesso amplo ao armazenamento externo, é possível que um invasor modifique arquivos e downloads silenciosamente, instale apps potencialmente maliciosos, negue serviço a apps principais ou cause falhas nos apps. Usuários maliciosos também podem manipular o que é enviado para Uri.parse() para fazer com que o usuário baixe um arquivo prejudicial.
Mitigações
Em vez de usar o DownloadManager, configure os downloads diretamente no app usando um cliente HTTP (como a Cronet), um programador/gerenciador de processos e uma maneira de garantir novas tentativas em caso de perda de rede. A documentação da biblioteca inclui um link para um exemplo de app, bem como instruções sobre como implementá-lo.
Se o aplicativo precisar gerenciar o agendamento de processos, executar
downloads em segundo plano ou tentar restabelecer o download após a perda de rede
, considere incluir WorkManager e
ForegroundServices.
Confira abaixo um exemplo de código para configurar um download usando a Cronet, extraído de o 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
- Página principal da documentação do DownloadManager
- Relatório de CVEs do DownloadManager
- CVE 2018-9468 de bypass de permissão do Android
- CVE-2018-9493 de injeção de SQL do provedor de downloads do Android
- CVE2018-9468 de bypass de permissão do provedor de downloads do Android
- Página principal da documentação da Cronet
- Instruções para usar a Cronet em um aplicativo
- Exemplo de implementação da Cronet
- Documentação do Uri
- Documentação do ForegroundService
- Documentação do WorkManager