หมวดหมู่ OWASP: MASVS-NETWORK: Network Communication
ภาพรวม
DownloadManager เป็นบริการของระบบที่เปิดตัวในระดับ API 9 ซึ่งจัดการการดาวน์โหลด HTTP ที่ใช้เวลานานและอนุญาตให้แอปพลิเคชันดาวน์โหลดไฟล์เป็นงานเบื้องหลัง API ของบริการนี้จัดการการโต้ตอบ HTTP และลองดาวน์โหลดซ้ำหลังจากเกิดข้อผิดพลาดหรือเมื่อมีการเปลี่ยนแปลงการเชื่อมต่อและการรีบูตระบบ
DownloadManager มีจุดอ่อนที่เกี่ยวข้องกับความปลอดภัยซึ่งทำให้เป็นตัวเลือกที่ไม่ปลอดภัยสำหรับการจัดการการดาวน์โหลดในแอปพลิเคชัน Android
(1) CVE ใน Download Provider
ในปี 2018 พบและแก้ไข CVE 3 รายการใน Download Provider ข้อมูลสรุปของแต่ละรายการมีดังนี้ (ดู รายละเอียดทางเทคนิค)
- การข้ามการให้สิทธิ์ Download Provider \- แอปที่เป็นอันตรายสามารถดึงข้อมูลทั้งหมดจาก Download Provider ได้โดยไม่มีการให้สิทธิ์ ซึ่งอาจรวมถึงข้อมูลที่อาจละเอียดอ่อน เช่น ชื่อไฟล์ คำอธิบาย ชื่อ เส้นทาง URL รวมถึงสิทธิ์ READ/WRITE แบบเต็มสำหรับไฟล์ที่ดาวน์โหลดทั้งหมด แอปที่เป็นอันตรายสามารถทำงานในเบื้องหลัง ตรวจสอบการดาวน์โหลดทั้งหมด และเปิดเผยเนื้อหาจากระยะไกล หรือแก้ไขไฟล์แบบเรียลไทม์ก่อนที่ผู้ขอที่ถูกต้องจะเข้าถึง ซึ่งอาจทำให้ผู้ใช้ไม่สามารถใช้งานแอปพลิเคชันหลักได้ รวมถึงไม่สามารถดาวน์โหลดการอัปเดต
- การแทรก SQL ของ Download Provider \- แอปพลิเคชันที่เป็นอันตรายที่ไม่มีสิทธิ์สามารถดึงข้อมูลทั้งหมดจาก Download Provider ผ่านช่องโหว่การแทรก SQL นอกจากนี้ แอปพลิเคชันที่มีสิทธิ์จำกัด เช่น
android.permission.INTERNETยังสามารถเข้าถึงเนื้อหาฐานข้อมูลทั้งหมดจาก URI อื่นได้ด้วย ระบบอาจดึงข้อมูลที่อาจละเอียดอ่อน เช่น ชื่อไฟล์ คำอธิบาย ชื่อ เส้นทาง URL และอาจเข้าถึงเนื้อหาที่ดาวน์โหลดได้ด้วย ทั้งนี้ขึ้นอยู่กับสิทธิ์ - การเปิดเผยข้อมูลส่วนหัวของคำขอ Download Provider \- แอปพลิเคชันที่เป็นอันตราย
ที่ได้รับสิทธิ์
android.permission.INTERNETสามารถดึงข้อมูลทั้งหมดจากตารางส่วนหัวของคำขอ Download Provider ส่วนหัวเหล่านี้อาจมีข้อมูลที่ละเอียดอ่อน เช่น คุกกี้ของเซสชันหรือส่วนหัวการตรวจสอบสิทธิ์สำหรับการดาวน์โหลดที่เริ่มต้นจาก Android Browser หรือ Google Chrome รวมถึงแอปพลิเคชันอื่นๆ ซึ่งอาจอนุญาตให้ผู้โจมตีแอบอ้างเป็นผู้ใช้ในแพลตฟอร์มใดก็ตามที่ได้รับข้อมูลผู้ใช้ที่ละเอียดอ่อน
(2) สิทธิ์ที่เป็นอันตราย
DownloadManager ใน API ระดับต่ำกว่า 29 ต้องใช้สิทธิ์ที่เป็นอันตราย
android.permission.WRITE_EXTERNAL_STORAGE. สำหรับ API ระดับ 29
ขึ้นไป android.permission.WRITE_EXTERNAL_STORAGE
ไม่จำเป็นต้องมีสิทธิ์ แต่ URI ต้องอ้างอิงเส้นทางภายใน
ไดเรกทอรีที่เป็นของแอปพลิเคชันหรือเส้นทางภายในไดเรกทอรี "Downloads"
ระดับบนสุด
(3) การพึ่งพา Uri.parse()
DownloadManager อาศัยเมธอด Uri.parse() เพื่อแยกวิเคราะห์ตำแหน่งของการดาวน์โหลดที่ขอ คลาส Uri ใช้การตรวจสอบอินพุตที่ไม่น่าเชื่อถือเพียงเล็กน้อยหรือไม่ใช้เลยเพื่อประสิทธิภาพ
ผลกระทบ
การใช้ DownloadManager อาจนำไปสู่ช่องโหว่ผ่านการใช้สิทธิ์ WRITE กับพื้นที่เก็บข้อมูลภายนอก เนื่องจากสิทธิ์ android.permission.WRITE_EXTERNAL_STORAGE อนุญาตให้เข้าถึงพื้นที่จัดเก็บข้อมูลภายนอกได้อย่างกว้างขวาง ผู้โจมตีจึงสามารถแก้ไขไฟล์และการดาวน์โหลด ติดตั้งแอปที่อาจเป็นอันตราย ปฏิเสธการให้บริการแอปหลัก หรือทำให้แอปขัดข้องได้โดยไม่แสดงอาการ ผู้ไม่ประสงค์ดีอาจจัดการสิ่งที่ส่งไปยัง Uri.parse() เพื่อทำให้ผู้ใช้ดาวน์โหลดไฟล์ที่เป็นอันตราย
การบรรเทาผลกระทบ
ตั้งค่าการดาวน์โหลดในแอปโดยตรงโดยใช้ไคลเอ็นต์ HTTP (เช่น Cronet), ตัวกำหนดเวลา/ตัวจัดการกระบวนการ และวิธีตรวจสอบการลองใหม่หากเครือข่ายขาดหายไป แทนที่จะใช้ DownloadManager เอกสารประกอบของไลบรารีมี ลิงก์ไปยังแอปตัวอย่าง รวมถึงวิธีการติดตั้งใช้งาน
หากแอปพลิเคชันของคุณต้องมีความสามารถในการจัดการการกำหนดเวลากระบวนการ เรียกใช้
การดาวน์โหลดในเบื้องหลัง หรือลองสร้างการดาวน์โหลดใหม่หลังจากเครือข่าย
ขาดหายไป ให้พิจารณารวม WorkManager และ
ForegroundServices
ตัวอย่างโค้ดสำหรับการตั้งค่าการดาวน์โหลดโดยใช้ Cronet มีดังนี้ ซึ่งนำมาจาก Cronet Codelab
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();
});
}
แหล่งข้อมูล
- หน้าเอกสารประกอบหลักสำหรับ DownloadManager
- รายงาน CVE ของ DownloadManager
- การข้ามการให้สิทธิ์ Android CVE 2018-9468
- การแทรก SQL ของ Android Download Provider CVE-2018-9493
- การข้ามการให้สิทธิ์ Android Download Provider CVE2018-9468
- หน้าเอกสารประกอบหลักสำหรับ Cronet
- วิธีการใช้ Cronet ในแอปพลิเคชัน
- ตัวอย่างการติดตั้งใช้งาน Cronet
- เอกสารประกอบสำหรับ Uri
- เอกสารประกอบสำหรับ ForegroundService
- เอกสารประกอบสำหรับ WorkManager