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 API cấp 9. Thư viện 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 tác vụ trong nền. API của ứng dụng này xử lý các lượt tương tác HTTP và thử tải xuống lại sau khi xảy ra lỗi hoặc khi có thay đổi về kết nối và khởi động lại hệ thống.

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

(1) CVE trong Trình cung cấp nội dung tải xuống

Năm 2018, chúng tôi đã phát hiện và vá 3 CVE trong Trình cung cấp tải xuống. Dưới đây là nội dung tóm tắt từng nội dung (xem thông tin kỹ thuật chi tiết).

  • Bỏ qua quyền của trình cung cấp tải xuống – Khi không được cấp các quyền nào, ứng dụng độc hại có thể truy xuất mọi mục nhập từ Trình cung cấp tải xuống. Những ứng dụng này có thể bao gồm thông tin nhạy cảm có thể chứa 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 mọi tệp đã tải xuống. Ứng dụng độc hại có thể chạy trong nền, theo dõi tất cả nội dung tải xuống và làm rò rỉ nội dung từ xa, hoặc sửa đổi các tệp một cách nhanh chóng 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ể khiến người dùng gặp phải tình trạng từ chối dịch vụ đối với các ứng dụng chính, bao gồm cả việc không thể tải bản cập nhật xuống.
  • Chèn SQL vào Trình cung cấp nội dung 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ả mục nhập từ Trình cung cấp nội dung 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 cơ sở dữ liệu từ một URI khác. Có thể truy xuất thông tin có thể nhạy cảm như tên tệp, nội dung mô tả, tiêu đề, đường dẫn, URL và tuỳ thuộc vào quyền, có thể truy cập vào nội dung đã tải xuống.
  • Tiết lộ thông tin về tiêu đề yêu cầu của Nhà cung cấp nội dung tải xuống – Một ứng dụng độc hại được cấp quyền android.permission.INTERNET có thể truy xuất tất cả các mục từ bảng tiêu đề yêu cầu của Nhà cung cấp nội dung 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, đối với 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 bất kỳ nền tảng nào mà dữ liệu nhạy cảm của người dùng được lấy từ đó.

(2) Các 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 API cấp 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 do ứng dụng sở hữu hoặc một đường dẫn trong thư mục "Tải xuống" cấp cao nhất.

(3) Sự phụ thuộc 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 áp dụng ít hoặc không xác thực 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 lợi dụng quyền GHI vào bộ nhớ ngoài. Vì quyền android.permission.WRITE_EXTERNAL_STORAGE cho phép quyền 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 ứng dụng gặp sự cố. Các tác nhân độc hại 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 gây hại xuống.

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

Thay vì sử dụng DownloadManager, hãy thiết lập tính năng tải xuống trực tiếp trong ứng dụng bằng cách sử dụng ứng dụng HTTP (chẳng hạn như Cronet), trình lập lịch biểu/trình quản lý quy trình và một cách để đảm bảo thử lại nếu bị mất mạng. Tài liệu về thư viện bao gồm một đường liên kết đến ứ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 yêu cầu khả năng quản lý việc lên lịch quy trình, chạy tải xuống ở chế độ nền hoặc thử lại việc thiết lập tải xuống sau khi mất mạng, hãy cân nhắc đưa vào WorkManagerForegroundServices.

Mã ví dụ để thiết lập quá trình tải xuống bằng Cronet như sau, được lấy từ lớp học lập trình về 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