หมวดหมู่ OWASP: MASVS-NETWORK: การสื่อสารผ่านเครือข่าย
ภาพรวม
DownloadManager เป็นบริการของระบบที่เปิดตัวใน API ระดับ 9 ซึ่งจัดการการดาวน์โหลด HTTP ที่ใช้เวลานาน และอนุญาตให้แอปพลิเคชันดาวน์โหลดไฟล์เป็นงานในเบื้องหลังได้ API ของบริการนี้จะจัดการการโต้ตอบ HTTP และพยายามดาวน์โหลดอีกครั้งหลังจากดำเนินการไม่สำเร็จ หรือเมื่อการเชื่อมต่อมีการเปลี่ยนแปลงและการรีบูตระบบ
DownloadManager มีจุดอ่อนด้านความปลอดภัยที่ทำให้เป็นตัวเลือกที่ไม่ปลอดภัยสำหรับการจัดการการดาวน์โหลดในแอปพลิเคชัน Android
(1) CVE ในผู้ให้บริการดาวน์โหลด
ในปี 2018 พบ CVE 3 รายการและทำการแก้ไขใน DownloadProvider สรุปของแต่ละวิธีมีดังนี้ (ดูรายละเอียดทางเทคนิค)
- การข้ามสิทธิ์ผู้ให้บริการดาวน์โหลด – เมื่อไม่ได้รับอนุญาต แอปที่เป็นอันตรายจะดึงรายการทั้งหมดจากผู้ให้บริการดาวน์โหลดได้ ซึ่งอาจรวมถึงข้อมูลที่อาจละเอียดอ่อน เช่น ชื่อไฟล์, คำอธิบาย, ชื่อ, เส้นทาง, URL รวมถึงสิทธิ์ในการอ่าน/เขียนแบบเต็มสำหรับไฟล์ที่ดาวน์โหลดทั้งหมด แอปที่เป็นอันตรายอาจทำงานในเบื้องหลัง ตรวจสอบการดาวน์โหลดทั้งหมดและรั่วไหลเนื้อหาจากระยะไกล หรือแก้ไขไฟล์ขณะดำเนินการก่อนที่จะมีผู้ขอเข้าถึงที่ถูกต้อง ซึ่งอาจทำให้ผู้ใช้ปฏิเสธการให้บริการสำหรับแอปพลิเคชันหลัก รวมถึงดาวน์โหลดอัปเดตไม่ได้
- ดาวน์โหลดการแทรก SQL ของผู้ให้บริการ – แอปพลิเคชันที่เป็นอันตรายซึ่งไม่มีสิทธิ์เรียกข้อมูลรายการทั้งหมดจากผู้ให้บริการดาวน์โหลดผ่านช่องโหว่ในการแทรก 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 มีดังนี้ ซึ่งนำมาจาก codelab ของ 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();
});
}
แหล่งข้อมูล
- หน้าเอกสารประกอบหลักของ DownloadManager
- รายงานสำหรับ DownloadManager CVE
- การข้ามสิทธิ์ของ Android CVE 2018-9468
- การแทรก SQL ของผู้ให้บริการดาวน์โหลด Android CVE-2018-9493
- การข้ามสิทธิ์ของผู้ให้บริการดาวน์โหลด Android CVE2018-9468
- หน้าเอกสารประกอบหลักสำหรับ Cronet
- วิธีการใช้ Cronet ในแอปพลิเคชัน
- ตัวอย่างการติดตั้งใช้งาน Cronet
- เอกสารประกอบสำหรับ URI
- เอกสารประกอบสำหรับ ForegroundService
- เอกสารประกอบสำหรับ WorkManager