Pengelola Download Tidak Aman

Kategori OWASP: MASVS-NETWORK: Komunikasi Jaringan

Ringkasan

DownloadManager adalah layanan sistem yang diperkenalkan di level API 9. Download HTTP yang berjalan lama ditangani dan aplikasi dapat mendownload file sebagai tugas latar belakang. API-nya menangani interaksi HTTP dan mencoba mendownload kembali setelah mengalami kegagalan atau saat melewati perubahan konektivitas dan reboot sistem.

DownloadManager memiliki kelemahan terkait keamanan yang membuatnya menjadi pilihan yang tidak aman untuk mengelola download di aplikasi Android.

(1) CVE di Penyedia Download

Pada tahun 2018, tiga CVE ditemukan dan di-patch di Download Provider. Berikut ringkasan masing-masing (lihat detail teknis).

  • Pelewatan Izin Penyedia Download – Tanpa izin yang diberikan, aplikasi berbahaya dapat mengambil semua entri dari Penyedia Download, yang dapat mencakup informasi yang berpotensi sensitif seperti nama file, deskripsi, judul, jalur, URL, serta izin BACA/TULIS penuh ke semua file yang didownload. Aplikasi berbahaya dapat berjalan di latar belakang, memantau semua download dan membocorkan kontennya dari jarak jauh, atau memodifikasi file secara langsung sebelum diakses oleh pemohon yang sah. Hal ini dapat menyebabkan penolakan layanan bagi pengguna untuk aplikasi inti, termasuk ketidakmampuan untuk mendownload update.
  • Injeksi SQL Penyedia Download – Melalui kerentanan injeksi SQL, aplikasi berbahaya tanpa izin dapat mengambil semua entri dari Penyedia Download. Selain itu, aplikasi dengan izin terbatas, seperti android.permission.INTERNET, juga dapat mengakses semua konten database dari URI yang berbeda. Informasi yang berpotensi sensitif seperti nama file, deskripsi, judul, jalur, URL dapat diambil, dan, bergantung pada izin, akses ke konten yang didownload juga dapat dilakukan.
  • Pengungkapan Informasi Header Permintaan Penyedia Download – Aplikasi berbahaya dengan izin android.permission.INTERNET yang diberikan dapat mengambil semua entri dari tabel header permintaan Penyedia Download. Header ini dapat mencakup informasi sensitif, seperti cookie sesi atau header autentikasi, untuk setiap download yang dimulai dari Browser Android atau Google Chrome, di antara aplikasi lainnya. Hal ini dapat memungkinkan penyerang meniru identitas pengguna di platform mana pun tempat data pengguna sensitif diperoleh.

(2) Izin Berbahaya

DownloadManager di level API yang lebih rendah dari 29 memerlukan izin berbahaya – android.permission.WRITE_EXTERNAL_STORAGE. Untuk level API 29 dan yang lebih tinggi, izin android.permission.WRITE_EXTERNAL_STORAGE tidak diperlukan, tetapi URI harus merujuk ke jalur dalam direktori yang dimiliki oleh aplikasi atau jalur dalam direktori "Download" level teratas.

(3) Ketergantungan pada Uri.parse()

DownloadManager mengandalkan metode Uri.parse() untuk mengurai lokasi download yang diminta. Untuk kepentingan performa, class Uri menerapkan sedikit atau tidak ada validasi pada input yang tidak tepercaya.

Dampak

Penggunaan DownloadManager dapat menyebabkan kerentanan melalui eksploitasi izin WRITE ke penyimpanan eksternal. Karena izin android.permission.WRITE_EXTERNAL_STORAGE memungkinkan akses luas ke penyimpanan eksternal, penyerang dapat memodifikasi file dan download secara diam-diam, menginstal aplikasi yang berpotensi berbahaya, menolak layanan ke aplikasi inti, atau menyebabkan aplikasi mengalami error. Pihak-pihak berniat jahat juga dapat memanipulasi apa yang dikirim ke Uri.parse() untuk menyebabkan pengguna mendownload file berbahaya.

Mitigasi

Daripada menggunakan DownloadManager, siapkan download langsung di aplikasi Anda menggunakan klien HTTP (seperti Cronet), penjadwal/pengelola proses, dan cara untuk memastikan pencobaan ulang jika terjadi kehilangan jaringan. Dokumentasi library mencakup link ke aplikasi contoh serta petunjuk tentang cara mengimplementasikannya.

Jika aplikasi Anda memerlukan kemampuan untuk mengelola penjadwalan proses, menjalankan download di latar belakang, atau mencoba lagi membuat download setelah kehilangan jaringan, pertimbangkan untuk menyertakan WorkManager dan ForegroundServices.

Contoh kode untuk menyiapkan download menggunakan Cronet adalah sebagai berikut, yang diambil dari codelab 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();
    });
}

Resource