Gestionnaire de téléchargement non sécurisé

Catégorie OWASP : MASVS-NETWORK : communication réseau

Présentation

DownloadManager est un service système introduit dans le niveau d'API 9. Il gère les téléchargements HTTP de longue durée et permet aux applications de télécharger des fichiers en tant que tâche en arrière-plan. Son API gère les interactions HTTP et réessaie les téléchargements en cas d'échec, de modification de la connectivité ou de redémarrage du système.

DownloadManager présente des failles de sécurité qui le rendent peu sûr pour gérer les téléchargements dans les applications Android.

(1) Failles CVE dans le fournisseur de téléchargement

En 2018, trois CVE ont été détectées et corrigées dans Download Provider. Vous trouverez ci-dessous un résumé de chacune d'elles (voir les informations techniques).

  • Contournement des autorisations du fournisseur de téléchargement : sans autorisations accordées, une application malveillante peut récupérer toutes les entrées du fournisseur de téléchargement, ce qui peut inclure des informations potentiellement sensibles telles que les noms de fichiers, les descriptions, les titres, les chemins, les URL, ainsi que les autorisations de lecture/écriture complètes pour tous les fichiers téléchargés. Une application malveillante peut s'exécuter en arrière-plan, surveiller tous les téléchargements et divulguer leur contenu à distance, ou modifier les fichiers instantanément avant qu'ils ne soient consultés par la demande légitime. Cela peut entraîner un déni de service pour l'utilisateur pour les applications principales, y compris l'impossibilité de télécharger des mises à jour.
  • Injection SQL du fournisseur de téléchargement : grâce à une faille d'injection SQL, une application malveillante sans autorisation peut récupérer toutes les entrées du fournisseur de téléchargement. De plus, les applications disposant d'autorisations limitées, telles que android.permission.INTERNET, peuvent également accéder à l'ensemble du contenu de la base de données à partir d'un autre URI. Des informations potentiellement sensibles telles que les noms, descriptions, titres, chemins et URL des fichiers peuvent être récupérées. Selon les autorisations, l'accès aux contenus téléchargés peut également être possible.
  • Divulgation d'informations sur les en-têtes de requête du fournisseur de téléchargement : une application malveillante à laquelle l'autorisation android.permission.INTERNET a été accordée peut récupérer toutes les entrées de la table des en-têtes de requête du fournisseur de téléchargement. Ces en-têtes peuvent inclure des informations sensibles, telles que des cookies de session ou des en-têtes d'authentification, pour tout téléchargement démarré depuis le navigateur Android ou Google Chrome, entre autres applications. Cela pourrait permettre à un pirate informatique de se faire passer pour l'utilisateur sur n'importe quelle plate-forme à partir de laquelle des données utilisateur sensibles ont été obtenues.

(2) Autorisations dangereuses

DownloadManager dans les niveaux d'API inférieurs à 29 nécessite des autorisations dangereuses : android.permission.WRITE_EXTERNAL_STORAGE. Pour le niveau d'API 29 et versions ultérieures, les autorisations android.permission.WRITE_EXTERNAL_STORAGE ne sont pas requises, mais l'URI doit faire référence à un chemin d'accès dans les répertoires appartenant à l'application ou à un chemin d'accès dans le répertoire de premier niveau "Téléchargements".

(3) Recours à Uri.parse()

DownloadManager s'appuie sur la méthode Uri.parse() pour analyser l'emplacement du téléchargement demandé. Pour optimiser les performances, la classe Uri applique peu ou pas de validation sur des entrées non fiables.

Impact

L'utilisation de DownloadManager peut entraîner des failles en raison de l'exploitation des autorisations d'ÉCRIRE sur le stockage externe. Étant donné que les autorisations android.permission.WRITE_EXTERNAL_STORAGE permettent un accès étendu au stockage externe, un pirate informatique peut modifier de manière silencieuse des fichiers et des téléchargements, installer des applications potentiellement malveillantes, refuser le service aux applications principales ou provoquer le plantage d'applications. Des acteurs malveillants peuvent également manipuler ce qui est envoyé à Uri.parse() pour amener l'utilisateur à télécharger un fichier dangereux.

Stratégies d'atténuation

Au lieu d'utiliser DownloadManager, configurez les téléchargements directement dans votre application à l'aide d'un client HTTP (tel que Cronet), d'un planificateur/gestionnaire de processus et d'un moyen de garantir les nouvelles tentatives en cas de perte de réseau. La documentation de la bibliothèque comprend un lien vers un exemple d'application, ainsi que des instructions sur son implémentation.

Si votre application doit pouvoir gérer la planification des processus, exécuter des téléchargements en arrière-plan ou réessayer d'établir le téléchargement après une perte de réseau, envisagez d'inclure WorkManager et ForegroundServices.

Voici un exemple de code tiré de l'atelier de programmation Cronet pour configurer un téléchargement à l'aide 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();
    });
}

Ressources