Administrador de descargas no seguro

Categoría de OWASP: MASVS-NETWORK: Comunicación de red

Descripción general

DownloadManager es un servicio del sistema que se introdujo en el nivel de API 9. Controla las descargas HTTP de larga duración y permite que las aplicaciones descarguen archivos como una tarea en segundo plano. Su API controla las interacciones HTTP y reintenta las descargas después de fallas o durante cambios de conectividad y reinicios del sistema.

DownloadManager tiene debilidades relevantes para la seguridad que lo convierten en una opción no segura para administrar descargas en aplicaciones para Android.

(1) CVE en el proveedor de descargas

En 2018, se encontraron y corrigieron tres CVE en el proveedor de descargas. A continuación, se incluye un resumen de cada uno (consulta los detalles técnicos).

  • Omisión de permisos del proveedor de descargas : Sin permisos otorgados, una app maliciosa podría recuperar todas las entradas del proveedor de descargas, lo que podría incluir información potencialmente sensible, como nombres de archivos, descripciones, títulos, rutas de acceso, URLs, así como permisos de LECTURA/ESCRITURA completos para todos los archivos descargados. Una app maliciosa podría ejecutarse en segundo plano, supervisar todas las descargas y filtrar su contenido de forma remota, o modificar los archivos sobre la marcha antes de que el solicitante legítimo acceda a ellos. Esto podría causar una denegación de servicio para el usuario en aplicaciones principales, incluida la imposibilidad de descargar actualizaciones.
  • Inyección de SQL en el proveedor de descargas : A través de una vulnerabilidad de inyección de SQL, una aplicación maliciosa sin permisos podría recuperar todas las entradas del proveedor de descargas. Además, las aplicaciones con permisos limitados, como android.permission.INTERNET, también podrían acceder a todo el contenido de la base de datos desde un URI diferente. Se podría recuperar información potencialmente sensible, como nombres de archivos, descripciones, títulos, rutas de acceso y URLs, y, según los permisos, también podría ser posible acceder al contenido descargado.
  • Divulgación de información de los encabezados de solicitud del proveedor de descargas : Una aplicación maliciosa con el permiso android.permission.INTERNET otorgado podría recuperar todas las entradas de la tabla de encabezados de solicitud del proveedor de descargas. Estos encabezados pueden incluir información sensible, como cookies de sesión o encabezados de autenticación, para cualquier descarga iniciada desde el navegador de Android o Google Chrome, entre otras aplicaciones. Esto podría permitir que un atacante se haga pasar por el usuario en cualquier plataforma desde la que se obtuvieron datos sensibles del usuario.

(2) Permisos peligrosos

DownloadManager en niveles de API inferiores a 29 requiere permisos peligrosos – android.permission.WRITE_EXTERNAL_STORAGE. Para el nivel de API 29 y versiones posteriores, android.permission.WRITE_EXTERNAL_STORAGE no se requieren permisos, pero el URI debe hacer referencia a una ruta de acceso dentro de los directorios que posee la aplicación o a una ruta de acceso dentro del directorio "Descargas" de nivel superior.

(3) Dependencia de Uri.parse()

DownloadManager depende del método Uri.parse() para analizar la ubicación de la descarga solicitada. En aras del rendimiento, la clase Uri aplica poca o ninguna validación en la entrada no confiable.

Impacto

El uso de DownloadManager puede generar vulnerabilidades a través de la explotación de permisos de ESCRITURA en el almacenamiento externo. Dado que los permisos android.permission.WRITE_EXTERNAL_STORAGE permiten un acceso amplio al almacenamiento externo, es posible que un atacante modifique archivos y descargas de forma silenciosa, instale apps potencialmente maliciosas, deniegue el servicio a apps principales o provoque que las apps fallen. Los agentes maliciosos también podrían manipular lo que se envía a Uri.parse() para que el usuario descargue un archivo dañino.

Mitigaciones

En lugar de usar DownloadManager, configura las descargas directamente en tu app con un cliente HTTP (como Cronet), un planificador o administrador de procesos y una forma de garantizar los reintentos si se pierde la red. La documentación de la biblioteca incluye un vínculo a una app de ejemplo, así como instrucciones para implementarla.

Si tu aplicación requiere la capacidad de administrar la programación de procesos, ejecutar descargas en segundo plano o reintentar establecer la descarga después de la pérdida de red , considera incluir WorkManager y ForegroundServices.

El siguiente es un código de ejemplo para configurar una descarga con Cronet, extraído de l codelab de 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