مدیریت بارگیری ناامن

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

نمای کلی

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

DownloadManager ضعف‌های امنیتی دارد که آن را به انتخابی ناامن برای مدیریت بارگیری‌ها در برنامه‌های Android تبدیل می‌کند.

(۱) آسیب‌پذیری‌ها و افشاهای رایج در «ارائه‌دهنده بارگیری»

در سال ۲۰۱۸، سه آسیب‌پذیری و افشای رایج در «ارائه‌دهنده بارگیری» پیدا و وصله شد. خلاصه‌ای از هرکدام در ادامه آمده است (جزئیات فنی را ببینید).

  • دور زدن اجازه ارائه‌دهنده بارگیری – بدون اعطای اجازه‌ها، برنامه مخرب می‌تواند همه ورودی‌ها را از «ارائه‌دهنده بارگیری» بازیابی کند که می‌تواند شامل اطلاعات بالقوه حساس مانند نام فایل، شرح، عنوان، مسیر، نشانی وب، و همچنین اجازه‌های کامل «خواندن/نوشتن» برای همه فایل‌های بارگیری‌شده باشد. برنامه مخرب می‌تواند در پس‌زمینه اجرا شود، همه بارگیری‌ها را نظارت کند، و محتوای آن‌ها را از راه دور افشا کند، یا فایل‌ها را درحین انتقال و قبل‌از اینکه درخواست‌کننده قانونی به آن‌ها دسترسی پیدا کند تغییر دهد. این امر می‌تواند باعث شود کاربر نتواند از برنامه‌های اصلی استفاده کند، ازجمله اینکه نتواند به‌روزرسانی‌ها را بارگیری کند.
  • تزریق SQL به ارائه‌دهنده بارگیری – ازطریق آسیب‌پذیری تزریق SQL، یک برنامه مخرب بدون مجوز می‌تواند همه ورودی‌ها را از ارائه‌دهنده بارگیری بازیابی کند. همچنین، برنامه‌هایی با اجازه‌های محدود، مثل android.permission.INTERNET، می‌توانند به همه محتواهای پایگاه داده از URI دیگری دسترسی داشته باشند. اطلاعات بالقوه حساس مانند نام فایل‌ها، شرح‌ها، عنوان‌ها، مسیرها، و نشانی‌های وب ممکن است بازیابی شوند و بسته به اجازه‌ها، دسترسی به محتوای بارگیری‌شده نیز ممکن است امکان‌پذیر باشد.
  • افشای اطلاعات سرصفحه‌های درخواست ارائه‌دهنده بارگیری – یک برنامه مخرب با اجازه android.permission.INTERNET اعطاشده می‌تواند همه ورودی‌ها را از جدول سرصفحه‌های درخواست ارائه‌دهنده بارگیری بازیابی کند. این سرصفحه‌ها ممکن است حاوی اطلاعات حساسی مانند کوکی‌های جلسه یا سرصفحه‌های اصالت‌سنجی برای هر بارگیری‌ای باشد که از «مرورگر Android» یا Google Chrome، در میان برنامه‌های دیگر، شروع شده است. این امر می‌تواند به مهاجم اجازه دهد تا خود را به‌جای کاربر در هر پلاتفرمی که داده‌های حساس کاربر از آن به‌دست آمده است جا بزند.

(۲) اجازه‌های خطرناک

DownloadManager در سطوح API پایین‌تر از ۲۹ به اجازه‌های خطرناک نیاز دارد – android.permission.WRITE_EXTERNAL_STORAGE. برای سطح میانای برنامه‌سازی کاربردی ۲۹ و بالاتر، اجازه‌های android.permission.WRITE_EXTERNAL_STORAGE لازم نیست، اما URI باید به مسیری در دایرکتوری‌های متعلق به برنامه یا مسیری در دایرکتوری سطح بالای «بارگیری‌ها» اشاره کند.

(۳) اتکا به 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();
    });
}

منابع