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

Категория 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) Опасные разрешения

Для API-интерфейсов с уровнем ниже 29 требуются опасные разрешения – android.permission.WRITE_EXTERNAL_STORAGE . Для API-интерфейсов 29 и выше разрешения android.permission.WRITE_EXTERNAL_STORAGE не требуются, но URI должен указывать на путь внутри каталогов, принадлежащих приложению, или на путь внутри корневого каталога "Downloads".

(3) Опора на Uri.parse()

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

Влияние

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

Меры по смягчению последствий

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

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

Пример кода для настройки загрузки с помощью Cronet представлен ниже и взят из учебного пособия 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()
   }
}

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

Ресурсы