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();
});
}
리소스
- DownloadManager의 기본 문서 페이지
- DownloadManager CVE 신고
- Android 권한 우회 CVE 2018-9468
- Android 다운로드 제공업체 SQL 삽입 CVE-2018-9493
- Android 다운로드 제공업체 권한 우회 CVE2018-9468
- Cronet 기본 문서 페이지
- 애플리케이션에서 Cronet을 사용하는 방법에 관한 안내
- 샘플 Cronet 구현
- Uri 문서
- ForegroundService 문서
- WorkManager 문서