안전하지 않은 다운로드 관리자
컬렉션을 사용해 정리하기
내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.
OWASP 카테고리: MASVS-NETWORK: 네트워크 커뮤니케이션
개요
DownloadManager는 API 수준 9에서 도입된 시스템 서비스입니다. 장기 실행 HTTP 다운로드를 처리하고 애플리케이션이 파일을 백그라운드 작업으로 다운로드할 수 있도록 허용합니다. 이 API는 HTTP 상호작용을 처리하고 다운로드가 실패하거나 연결이 변경되거나 시스템이 재부팅된 후에 다운로드를 다시 시도합니다.
DownloadManager에는 보안과 관련된 약점이 있어 Android 애플리케이션에서 다운로드를 관리하기에 안전하지 않습니다.
(1) 다운로드 제공업체의 CVE
2018년에 DownloadProvider에서 세 가지 CVE가 발견되어 패치되었습니다. 각 방법에 대한 요약은 다음과 같습니다 (기술 세부정보 참고).
- 다운로드 제공업체 권한 우회 – 권한이 부여되지 않으면 악성 앱이 다운로드 제공업체의 모든 항목을 검색할 수 있습니다. 이러한 항목에는 파일 이름, 설명, 제목, 경로, URL과 같은 민감한 정보는 물론 다운로드한 모든 파일의 전체 읽기/쓰기 권한이 포함될 수 있습니다. 악성 앱이 백그라운드에서 실행되어 모든 다운로드를 모니터링하고 콘텐츠를 원격으로 유출하거나, 합법적인 요청자가 액세스하기 전에 파일을 즉시 수정할 수 있습니다. 이로 인해 업데이트를 다운로드할 수 없는 등 핵심 애플리케이션에서 서비스 거부가 발생할 수 있습니다.
- 다운로드 제공업체 SQL 삽입: SQL 삽입 취약점을 통해 권한이 없는 악성 애플리케이션이 다운로드 제공업체에서 모든 항목을 가져올 수 있습니다. 또한
android.permission.INTERNET
와 같이 권한이 제한된 애플리케이션은 다른 URI의 모든 데이터베이스 콘텐츠에 액세스할 수 있습니다. 파일 이름, 설명, 제목, 경로, URL과 같이 민감할 수 있는 정보가 검색될 수 있으며 권한에 따라 다운로드한 콘텐츠에 액세스할 수도 있습니다.
- 다운로드 제공업체 요청 헤더 정보 공개 –
android.permission.INTERNET
권한이 부여된 악성 애플리케이션이 다운로드 제공업체 요청 헤더 테이블에서 모든 항목을 검색할 수 있습니다. 이러한 헤더에는 Android 브라우저 또는 Google Chrome에서 시작된 다운로드(다른 애플리케이션 포함)에 관한 세션 쿠키 또는 인증 헤더와 같은 민감한 정보가 포함될 수 있습니다. 이렇게 하면 공격자가 민감한 사용자 데이터를 획득한 플랫폼에서 사용자를 명의 도용할 수 있습니다.
(2) 위험한 권한
API 수준이 29보다 낮은 DownloadManager에는 위험한 권한인 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을 사용하여 다운로드를 설정하는 예시 코드는 다음과 같습니다(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();
});
}
리소스
이 페이지에 나와 있는 콘텐츠와 코드 샘플에는 콘텐츠 라이선스에서 설명하는 라이선스가 적용됩니다. 자바 및 OpenJDK는 Oracle 및 Oracle 계열사의 상표 또는 등록 상표입니다.
최종 업데이트: 2025-07-26(UTC)
[[["이해하기 쉬움","easyToUnderstand","thumb-up"],["문제가 해결됨","solvedMyProblem","thumb-up"],["기타","otherUp","thumb-up"]],[["필요한 정보가 없음","missingTheInformationINeed","thumb-down"],["너무 복잡함/단계 수가 너무 많음","tooComplicatedTooManySteps","thumb-down"],["오래됨","outOfDate","thumb-down"],["번역 문제","translationIssue","thumb-down"],["샘플/코드 문제","samplesCodeIssue","thumb-down"],["기타","otherDown","thumb-down"]],["최종 업데이트: 2025-07-26(UTC)"],[],[],null,["# Unsafe Download Manager\n\n\u003cbr /\u003e\n\n**OWASP category:** [MASVS-NETWORK: Network Communication](https://mas.owasp.org/MASVS/08-MASVS-NETWORK)\n\nOverview\n--------\n\nDownloadManager is a system service introduced in API level 9. It handles\nlong-running HTTP downloads and allows applications to download files as a\nbackground task. Its API handles HTTP interactions and retries downloads after\nfailures or across connectivity changes and system reboots.\n\nDownloadManager has security relevant weaknesses that make it an insecure choice\nfor managing downloads in Android applications.\n\n**(1) CVEs in Download Provider**\n\nIn 2018, three [CVEs](https://ioactive.com/multiple-vulnerabilities-in-androids-download-provider-cve-2018-9468-cve-2018-9493-cve-2018-9546/) were found and patched in Download\nProvider. A summary of each follows (see [technical details](https://ioactive.com/multiple-vulnerabilities-in-androids-download-provider-cve-2018-9468-cve-2018-9493-cve-2018-9546/)).\n\n- **Download Provider Permission Bypass** -- With no granted permissions, a malicious app could retrieve all entries from the Download Provider, which could include potentially sensitive information such as file names, descriptions, titles, paths, URLs, as well as full READ/WRITE permissions to all downloaded files. A malicious app could run in the background, monitoring all downloads and leaking their contents remotely, or modifying the files on-the-fly before they are accessed by the legitimate requester. This could cause a denial-of-service for the user for core applications, including the inability to download updates.\n- **Download Provider SQL Injection** -- Through a SQL injection vulnerability, a malicious application with no permissions could retrieve all entries from the Download Provider. Also, applications with limited permissions, such as [`android.permission.INTERNET`](http://go/android-dev/reference/android/Manifest.permission#INTERNET), could also access all database contents from a different URI. Potentially sensitive information such as file names, descriptions, titles, paths, URLs could be retrieved, and, depending on permissions, access to downloaded contents may be possible as well.\n- **Download Provider Request Headers Information Disclosure** -- A malicious application with the [`android.permission.INTERNET`](http://go/android-dev/reference/android/Manifest.permission#INTERNET) permission granted could retrieve all entries from the Download Provider request headers table. These headers may include sensitive information, such as session cookies or authentication headers, for any download started from the Android Browser or Google Chrome, among other applications. This could allow an attacker to impersonate the user on any platform from which sensitive user data was obtained.\n\n**(2) Dangerous Permissions**\n\nDownloadManager in API levels lower than 29 requires dangerous permissions --\n[`android.permission.WRITE_EXTERNAL_STORAGE`](http://go/android-dev/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE). For API level 29\nand higher, [`android.permission.WRITE_EXTERNAL_STORAGE`](http://go/android-dev/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE)\npermissions are not required, but the URI must refer to a path within the\ndirectories owned by the application or a path within the top-level \"Downloads\"\ndirectory.\n\n**(3) Reliance on** `Uri.parse()`\n\nDownloadManager relies on the `Uri.parse()` method to parse the location of the\nrequested download. In the interest of performance, the `Uri` class applies\nlittle to no validation on untrusted input.\n\nImpact\n------\n\nUsing DownloadManager may lead to vulnerabilities through the exploitation of\nWRITE permissions to external storage. Since\nandroid.permission.WRITE_EXTERNAL_STORAGE permissions allow broad access to\nexternal storage, it is possible for an attacker to silently modify files and\ndownloads, install potentially malicious apps, deny service to core apps, or\ncause apps to crash. Malicious actors could also manipulate what is sent to\nUri.parse() to cause the user to download a harmful file.\n\nMitigations\n-----------\n\nInstead of using DownloadManager, set up downloads directly in your app using an\nHTTP client (such as Cronet), a process scheduler/manager, and a way to ensure\nretries if there is network loss. The [documentation of the library](/develop/connectivity/cronet) includes\na link to a [sample](https://github.com/GoogleChromeLabs/cronet-sample) app as well as [instructions](/develop/connectivity/cronet/start) on how\nto implement it.\n\nIf your application requires the ability to manage process scheduling, run\ndownloads in the background, or retry establishing the download after network\nloss, then consider including [`WorkManager`](/reference/androidx/work/WorkManager) and\n[`ForegroundServices`](/develop/background-work/services/foreground-services).\n\nExample code for setting up a download using Cronet is as follows, taken from\nthe Cronet [codelab](/codelabs/cronet#8). \n\n### Kotlin\n\n override suspend fun downloadImage(url: String): ImageDownloaderResult {\n val startNanoTime = System.nanoTime()\n return suspendCoroutine {\n cont -\u003e\n val request = engine.newUrlRequestBuilder(url, object: ReadToMemoryCronetCallback() {\n override fun onSucceeded(\n request: UrlRequest,\n info: UrlResponseInfo,\n bodyBytes: ByteArray) {\n cont.resume(ImageDownloaderResult(\n successful = true,\n blob = bodyBytes,\n latency = Duration.ofNanos(System.nanoTime() - startNanoTime),\n wasCached = info.wasCached(),\n downloaderRef = this@CronetImageDownloader))\n }\n override fun onFailed(\n request: UrlRequest,\n info: UrlResponseInfo,\n error: CronetException\n ) {\n Log.w(LOGGER_TAG, \"Cronet download failed!\", error)\n cont.resume(ImageDownloaderResult(\n successful = false,\n blob = ByteArray(0),\n latency = Duration.ZERO,\n wasCached = info.wasCached(),\n downloaderRef = this@CronetImageDownloader))\n }\n }, executor)\n request.build().start()\n }\n }\n\n### Java\n\n @Override\n public CompletableFuture\u003cImageDownloaderResult\u003e downloadImage(String url) {\n long startNanoTime = System.nanoTime();\n return CompletableFuture.supplyAsync(() -\u003e {\n UrlRequest.Builder requestBuilder = engine.newUrlRequestBuilder(url, new ReadToMemoryCronetCallback() {\n @Override\n public void onSucceeded(UrlRequest request, UrlResponseInfo info, byte[] bodyBytes) {\n return ImageDownloaderResult.builder()\n .successful(true)\n .blob(bodyBytes)\n .latency(Duration.ofNanos(System.nanoTime() - startNanoTime))\n .wasCached(info.wasCached())\n .downloaderRef(CronetImageDownloader.this)\n .build();\n }\n @Override\n public void onFailed(UrlRequest request, UrlResponseInfo info, CronetException error) {\n Log.w(LOGGER_TAG, \"Cronet download failed!\", error);\n return ImageDownloaderResult.builder()\n .successful(false)\n .blob(new byte[0])\n .latency(Duration.ZERO)\n .wasCached(info.wasCached())\n .downloaderRef(CronetImageDownloader.this)\n .build();\n }\n }, executor);\n UrlRequest urlRequest = requestBuilder.build();\n urlRequest.start();\n return urlRequest.getResult();\n });\n }\n\nResources\n---------\n\n- [Main documentation page for DownloadManager](/reference/android/app/DownloadManager)\n- [Report for DownloadManager CVEs](https://ioactive.com/multiple-vulnerabilities-in-androids-download-provider-cve-2018-9468-cve-2018-9493-cve-2018-9546/)\n- [Android Permission Bypass CVE 2018-9468](https://ioactive.com/wp-content/uploads/2019/04/IOActive-Security-Advisory-Androids-Download-Provider-Permission-Bypass-CVE-2018-9468.pdf)\n- [Android Download Provider SQL Injection CVE-2018- 9493](https://act-on.ioactive.com/acton/attachment/34793/f-722b41b4-7aff-4b35-9925-c221a217744d/1/-/-/-/-/cve-2018-9493.pdf)\n- [Android Download Provider Permission Bypass CVE2018-9468](https://act-on.ioactive.com/acton/attachment/34793/f-3b8bb46b-d105-4efd-97a1-9970bfa6928b/1/-/-/-/-/cve-2018-9546.pdf)\n- [Main documentation page for Cronet](/develop/connectivity/cronet)\n- [Instructions for using Cronet in an application](/develop/connectivity/cronet/start#java)\n- [Sample Cronet implementation](https://github.com/GoogleChromeLabs/cronet-sample)\n- [Documentation for Uri](/reference/android/net/Uri)\n- [Documentation for ForegroundService](/develop/background-work/services/foreground-services)\n- [Documentation for WorkManager](/reference/androidx/work/WorkManager)"]]