安全でないダウンロード マネージャー
コレクションでコンテンツを整理
必要に応じて、コンテンツの保存と分類を行います。
OWASP カテゴリ: MASVS-NETWORK: ネットワーク通信
概要
DownloadManager は、API レベル 9 で導入されたシステム サービスです。長時間実行される HTTP ダウンロードを処理し、アプリケーションがバックグラウンド タスクとしてファイルをダウンロードできるようにします。この API は HTTP インタラクションを処理し、障害解消後、または接続変更およびシステム再起動後にダウンロードを再試行します。
DownloadManager にはセキュリティに関連する弱点があるため、Android アプリでダウンロードを管理するには安全ではありません。
(1)ダウンロード プロバイダの CVE
2018 年に、ダウンロード プロバイダで 3 つの CVE が見つかり、パッチが適用されました。それぞれの概要を以下に示します(技術的な詳細をご覧ください)。
- ダウンロード プロバイダの権限のバイパス - 権限が付与されていない場合、悪意のあるアプリはダウンロード プロバイダからすべてのエントリを取得できます。これには、ファイル名、説明、タイトル、パス、URL などの機密情報や、ダウンロードされたすべてのファイルに対する完全な読み取り/書き込み権限が含まれる可能性があります。悪意のあるアプリがバックグラウンドで実行され、すべてのダウンロードをモニタリングしてリモートで内容が漏洩する可能性があります。また、正当なリクエスト元がファイルにアクセスする前に、その場でファイルを変更することもできます。これにより、コア アプリケーションのユーザーに対してサービス拒否が発生する可能性があります(アップデートのダウンロードが不可能になるなど)。
- ダウンロード プロバイダの SQL インジェクション - SQL インジェクションの脆弱性により、権限のない悪意のあるアプリがダウンロード プロバイダからすべてのエントリを取得する可能性があります。また、権限が制限されているアプリ(
android.permission.INTERNET
など)は、別の URI からデータベースのすべてのコンテンツにアクセスすることもできます。ファイル名、説明、タイトル、パス、URL などの機密情報の取得が可能になり、権限によっては、ダウンロードしたコンテンツへのアクセスも可能になる可能性があります。
- ダウンロード プロバイダのリクエスト ヘッダー情報の漏洩 -
android.permission.INTERNET
権限が付与された悪意のあるアプリケーションは、ダウンロード プロバイダのリクエスト ヘッダー テーブルからすべてのエントリを取得できます。これらのヘッダーには、Android ブラウザや Google Chrome などのアプリから開始されたダウンロードに関するセッション Cookie や認証ヘッダーなどの機密情報が含まれる場合があります。これにより、攻撃者は、機密性の高いユーザーデータが取得された任意のプラットフォームでユーザーになりすますことができます。
(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 を使用すると、外部ストレージに対する書き込み権限が悪用され、脆弱性が生じる可能性があります。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();
});
}
リソース
このページのコンテンツやコードサンプルは、コンテンツ ライセンスに記載のライセンスに従います。Java および OpenJDK は 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)"]]