مدیر دانلود ناامن

دسته OWASP: MASVS-NETWORK: Network Communication

نمای کلی

DownloadManager یک سرویس سیستمی است که در سطح 9 API معرفی شده است. بارگیری های طولانی مدت HTTP را مدیریت می کند و به برنامه ها اجازه می دهد فایل ها را به عنوان یک کار پس زمینه بارگیری کنند. API آن تعاملات HTTP را مدیریت می کند و پس از خرابی یا در طول تغییرات اتصال و راه اندازی مجدد سیستم، بارگیری را دوباره امتحان می کند.

DownloadManager دارای نقاط ضعف مرتبط با امنیت است که آن را به انتخابی ناامن برای مدیریت دانلودها در برنامه های اندروید تبدیل می کند.

(1) CVE در ارائه دهنده دانلود

در سال 2018، سه CVE در ارائه دهنده دانلود پیدا و وصله شد. خلاصه ای از هر یک در ادامه می آید (به جزئیات فنی مراجعه کنید).

  • دور زدن مجوز ارائه‌دهنده دانلود – بدون هیچ مجوزی، یک برنامه مخرب می‌تواند تمام ورودی‌های ارائه‌دهنده دانلود را بازیابی کند، که می‌تواند شامل اطلاعات بالقوه حساس مانند نام فایل‌ها، توضیحات، عنوان‌ها، مسیرها، آدرس‌های اینترنتی و همچنین مجوزهای کامل READ/WRITE باشد. تمام فایل های دانلود شده یک برنامه مخرب می‌تواند در پس‌زمینه اجرا شود، همه بارگیری‌ها را زیر نظر بگیرد و محتویات آن‌ها را از راه دور فاش کند، یا فایل‌ها را در لحظه قبل از دسترسی درخواست‌کننده قانونی اصلاح کند. این می تواند باعث انکار سرویس برای کاربر برای برنامه های اصلی شود، از جمله ناتوانی در دانلود به روز رسانی.
  • Download Provider SQL Injection – از طریق یک آسیب‌پذیری تزریق SQL، یک برنامه مخرب بدون مجوز می‌تواند همه ورودی‌ها را از ارائه‌دهنده دانلود بازیابی کند. همچنین، برنامه‌هایی با مجوزهای محدود، مانند android.permission.INTERNET ، همچنین می‌توانند از یک URI دیگر به تمام محتوای پایگاه داده دسترسی داشته باشند. اطلاعات بالقوه حساس مانند نام فایل ها، توضیحات، عنوان ها، مسیرها، URL ها می توانند بازیابی شوند و بسته به مجوزها، دسترسی به محتوای دانلود شده نیز ممکن است امکان پذیر باشد.
  • افشای اطلاعات سرصفحه درخواست ارائه دهنده دانلود - یک برنامه مخرب با مجوز android.permission.INTERNET اعطا شده می تواند همه ورودی ها را از جدول سرصفحه درخواست ارائه دهنده دانلود بازیابی کند. این سرصفحه‌ها ممکن است شامل اطلاعات حساسی مانند کوکی‌های جلسه یا سرصفحه‌های احراز هویت برای هر بارگیری شروع شده از مرورگر 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 گرفته شده است.

کاتلین

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

منابع