Trình quản lý tải xuống không an toàn

Danh mục OWASP: MASVS-NETWORK: Giao tiếp qua mạng

Tổng quan

DownloadManager là một dịch vụ hệ thống được giới thiệu trong cấp độ API 9. Dịch vụ này xử lý các lượt tải xuống HTTP diễn ra trong thời gian dài và cho phép các ứng dụng tải tệp xuống dưới dạng một tác vụ trong nền. API này xử lý các hoạt động tương tác HTTP và thử lại các lượt tải xuống sau khi xảy ra lỗi hoặc trong quá trình thay đổi kết nối và khởi động lại hệ thống.

DownloadManager có những điểm yếu liên quan đến bảo mật khiến đây là lựa chọn không an toàn để quản lý các lượt tải xuống trong ứng dụng Android.

(1) Các CVE trong Trình cung cấp dịch vụ tải xuống

Năm 2018, 3 CVE đã được tìm thấy và vá trong DownloadProvider. Sau đây là nội dung tóm tắt của từng loại (xem thông tin kỹ thuật).

  • Bỏ qua quyền của Trình cung cấp dịch vụ tải xuống – Nếu không được cấp quyền, một ứng dụng độc hại có thể truy xuất tất cả các mục từ Trình cung cấp dịch vụ tải xuống, có thể bao gồm thông tin nhạy cảm như tên tệp, nội dung mô tả, tiêu đề, đường dẫn, URL, cũng như quyền ĐỌC/GHI đầy đủ đối với tất cả các tệp đã tải xuống. Một ứng dụng độc hại có thể chạy ở chế độ nền, giám sát mọi nội dung tải xuống và rò rỉ nội dung của các nội dung đó từ xa hoặc sửa đổi các tệp ngay lập tức trước khi người yêu cầu hợp pháp truy cập vào các tệp đó. Điều này có thể gây ra tình trạng từ chối dịch vụ cho người dùng đối với các ứng dụng cốt lõi, bao gồm cả việc không thể tải bản cập nhật xuống.
  • Lỗ hổng Chèn SQL của Trình cung cấp dịch vụ tải xuống – Thông qua lỗ hổng chèn SQL, một ứng dụng độc hại không có quyền có thể truy xuất tất cả các mục từ Trình cung cấp dịch vụ tải xuống. Ngoài ra, các ứng dụng có quyền hạn chế, chẳng hạn như android.permission.INTERNET, cũng có thể truy cập vào tất cả nội dung trong cơ sở dữ liệu từ một URI khác. Thông tin có khả năng nhạy cảm như tên tệp, nội dung mô tả, tiêu đề, đường dẫn, URL có thể được truy xuất và tuỳ thuộc vào quyền, bạn cũng có thể truy cập vào nội dung đã tải xuống.
  • Tiết lộ thông tin về tiêu đề của yêu cầu từ Trình cung cấp dịch vụ tải xuống – Một ứng dụng độc hại có quyền android.permission.INTERNET được cấp có thể truy xuất tất cả các mục trong bảng tiêu đề của yêu cầu từ Trình cung cấp dịch vụ tải xuống. Các tiêu đề này có thể bao gồm thông tin nhạy cảm, chẳng hạn như cookie phiên hoặc tiêu đề xác thực, cho mọi nội dung tải xuống bắt đầu từ Trình duyệt Android hoặc Google Chrome, cùng với các ứng dụng khác. Điều này có thể cho phép kẻ tấn công mạo danh người dùng trên mọi nền tảng mà từ đó dữ liệu nhạy cảm của người dùng bị đánh cắp.

(2) Quyền nguy hiểm

DownloadManager ở các cấp độ API thấp hơn 29 yêu cầu các quyền nguy hiểm – android.permission.WRITE_EXTERNAL_STORAGE. Đối với cấp độ API 29 trở lên, bạn không bắt buộc phải có quyền android.permission.WRITE_EXTERNAL_STORAGE, nhưng URI phải tham chiếu đến một đường dẫn trong các thư mục thuộc sở hữu của ứng dụng hoặc một đường dẫn trong thư mục "Tải xuống" cấp cao nhất.

(3) Dựa vào Uri.parse()

DownloadManager dựa vào phương thức Uri.parse() để phân tích cú pháp vị trí của nội dung tải xuống được yêu cầu. Để đảm bảo hiệu suất, lớp Uri hầu như không áp dụng quy trình xác thực nào đối với dữ liệu đầu vào không đáng tin cậy.

Tác động

Việc sử dụng DownloadManager có thể dẫn đến các lỗ hổng bảo mật thông qua việc khai thác quyền GHI vào bộ nhớ ngoài. Vì quyền android.permission.WRITE_EXTERNAL_STORAGE cho phép truy cập rộng rãi vào bộ nhớ ngoài, nên kẻ tấn công có thể âm thầm sửa đổi các tệp và nội dung tải xuống, cài đặt các ứng dụng có khả năng độc hại, từ chối dịch vụ cho các ứng dụng cốt lõi hoặc khiến các ứng dụng gặp sự cố. Các đối tượng xấu cũng có thể thao túng nội dung được gửi đến Uri.parse() để khiến người dùng tải một tệp có hại xuống.

Giải pháp giảm thiểu

Thay vì dùng DownloadManager, hãy thiết lập các lượt tải xuống ngay trong ứng dụng bằng cách sử dụng một máy khách HTTP (chẳng hạn như Cronet), một bộ lập lịch/trình quản lý quy trình và một cách để đảm bảo thử lại nếu mất mạng. Tài liệu về thư viện bao gồm đường liên kết đến một ứng dụng mẫu cũng như hướng dẫn về cách triển khai ứng dụng đó.

Nếu ứng dụng của bạn cần có khả năng quản lý việc lập lịch xử lý, chạy các bản tải xuống ở chế độ nền hoặc thử lại việc thiết lập bản tải xuống sau khi mất mạng, hãy cân nhắc việc thêm WorkManagerForegroundServices.

Sau đây là mã ví dụ để thiết lập một lượt tải xuống bằng Cronet, lấy từ lớp học lập trình 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();
    });
}

Tài nguyên