Небезопасный менеджер загрузок

Категория OWASP: MASVS-NETWORK: Сетевая связь

Обзор

DownloadManager — это системная служба, представленная на уровне API 9. Она обрабатывает длительные загрузки HTTP и позволяет приложениям загружать файлы в качестве фоновой задачи. Его API обрабатывает HTTP-взаимодействия и повторяет загрузку после сбоев или при изменении подключения и перезагрузке системы.

DownloadManager имеет недостатки, связанные с безопасностью, которые делают его небезопасным выбором для управления загрузками в приложениях Android.

(1) CVE в поставщике загрузок

В 2018 году в Download Provider было обнаружено и исправлено три CVE . Краткое описание каждого из них следует (см. технические подробности ).

  • Обход разрешений поставщика загрузок . Без предоставленных разрешений вредоносное приложение может получить все записи от поставщика загрузок, которые могут включать потенциально конфиденциальную информацию, такую ​​как имена файлов, описания, заголовки, пути, URL-адреса, а также полные разрешения на ЧТЕНИЕ/ЗАПИСЬ для все загруженные файлы. Вредоносное приложение может работать в фоновом режиме, отслеживая все загрузки и удаленно передавая их содержимое, или изменяя файлы на лету до того, как к ним получит доступ законный запрашивающий. Это может привести к отказу в обслуживании основных приложений пользователя, включая невозможность загрузки обновлений.
  • SQL-инъекция поставщика загрузки. Благодаря уязвимости внедрения SQL-кода вредоносное приложение без разрешений могло получить все записи от поставщика загрузки. Кроме того, приложения с ограниченными разрешениями, такие как android.permission.INTERNET , также могут получать доступ ко всему содержимому базы данных из другого URI. Потенциально конфиденциальная информация, такая как имена файлов, описания, заголовки, пути, URL-адреса, может быть получена, и, в зависимости от разрешений, также может быть возможен доступ к загруженному содержимому.
  • Раскрытие информации в заголовках запросов поставщика загрузки. Вредоносное приложение с предоставленным разрешением android.permission.INTERNET может получить все записи из таблицы заголовков запросов поставщика загрузки. Эти заголовки могут включать конфиденциальную информацию, такую ​​как файлы cookie сеанса или заголовки аутентификации, для любой загрузки, запущенной из браузера Android или Google Chrome, а также из других приложений. Это может позволить злоумышленнику выдать себя за пользователя на любой платформе, с которой были получены конфиденциальные пользовательские данные.

(2) Опасные разрешения

DownloadManager на уровнях API ниже 29 требует опасных разрешений — android.permission.WRITE_EXTERNAL_STORAGE . Для уровня API 29 и выше разрешения android.permission.WRITE_EXTERNAL_STORAGE не требуются, но URI должен ссылаться на путь в каталогах, принадлежащих приложению, или на путь в каталоге «Загрузки» верхнего уровня.

(3) Использование Uri.parse()

DownloadManager использует метод Uri.parse() для анализа местоположения запрошенной загрузки. В интересах производительности класс Uri практически не применяет проверку ненадежных входных данных или вообще не применяет ее.

Влияние

Использование DownloadManager может привести к уязвимостям из-за использования разрешений WRITE на внешнее хранилище. Поскольку разрешения android.permission.WRITE_EXTERNAL_STORAGE предоставляют широкий доступ к внешнему хранилищу, злоумышленник может незаметно изменять файлы и загрузки, устанавливать потенциально вредоносные приложения, отказывать в обслуживании основным приложениям или вызывать сбой приложений. Злоумышленники также могут манипулировать тем, что отправляется в Uri.parse(), чтобы заставить пользователя загрузить вредоносный файл.

Смягчения

Вместо использования DownloadManager настройте загрузку непосредственно в своем приложении, используя HTTP-клиент (например, Cronet), планировщик/менеджер процессов и способ гарантировать повторные попытки в случае потери сети. Документация библиотеки включает ссылку на пример приложения, а также инструкции по его реализации.

Если вашему приложению требуется возможность управлять планированием процессов, запускать загрузки в фоновом режиме или повторять загрузку после потери сети, рассмотрите возможность включения WorkManager и ForegroundServices .

Пример кода для настройки загрузки с помощью Cronet следующий, взят из Codelab Cronet.

Котлин

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()
   }
}

Ява

@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();
    });
}

Ресурсы