외부 저장소에 저장된 민감한 정보
컬렉션을 사용해 정리하기
내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.
OWASP 카테고리: MASVS-STORAGE: 저장소
개요
Android 10(API 29) 이하를 타겟팅하는 애플리케이션은 범위 지정 저장소를 적용하지 않습니다. 즉, 외부 저장소에 저장된 모든 데이터에 READ_EXTERNAL_STORAGE
권한이 있는 다른 모든 애플리케이션에서 액세스할 수 있습니다.
영향
Android 10(API 29) 이하를 타겟팅하는 애플리케이션에서 민감한 정보가 외부 저장소에 저장된 경우 기기의 READ_EXTERNAL_STORAGE 권한이 있는 모든 애플리케이션에서 액세스할 수 있습니다. 이렇게 하면 악성 콘텐츠가
민감한 파일에 영구적 또는 일시적으로 조용히 액세스하는 애플리케이션
외부 저장소에 저장됩니다. 또한 외부 저장소의 콘텐츠는 시스템의 모든 앱에서 액세스할 수 있으므로 WRITE_EXTERNAL_STORAGE 권한도 선언하는 악성 애플리케이션은 외부 저장소에 저장된 파일을 조작하여 악성 데이터를 포함할 수 있습니다. 이 악성 데이터가 애플리케이션에 로드되면 사용자를 속이거나 코드 실행을 달성하도록 설계될 수 있습니다.
완화 조치
범위 지정 저장소(Android 10 이상)
Android 10
Android 10을 타겟팅하는 애플리케이션의 경우 개발자는
범위 지정 저장소 AndroidManifest.xml
파일에서 requestLegacyExternalStorage
플래그를 false로 설정하면 됩니다. 범위 지정 저장소를 사용하면 애플리케이션은 외부 저장소에서 직접 만든 파일 또는 MediaStore API를 사용하여 저장된 파일 유형(예: 오디오, 동영상)에만 액세스할 수 있습니다. 이
사용자 개인 정보 보호 및 보안 강화를 지원합니다.
Android 11 이상
Android 11 이상 버전을 타겟팅하는 애플리케이션의 경우 OS는 범위 지정 저장소 사용을 시행합니다. 즉, requestLegacyExternalStorage
플래그를 무시하고 애플리케이션의 외부 저장소를 원치 않는 액세스로부터 자동으로 보호합니다.
민감한 정보에 내부 저장소 사용
대상 Android 버전과 관계없이 애플리케이션의 민감한 정보는
항상 내부 저장소에 저장되어야 합니다. 내부 저장소에 대한 액세스는
Android 샌드박싱 덕분에 소유하고 있는 애플리케이션으로 자동 제한되므로
따라서 기기가 루팅되지 않았다면 안전한 것으로 간주될 수 있습니다.
민감한 정보 암호화
애플리케이션의 사용 사례에서 민감한 정보를 외부
데이터를 암호화해야 합니다 강력한 암호화 알고리즘은
Android 키 저장소를 사용하여 키를 안전하게 저장하는 것이 좋습니다.
일반적으로 민감한 정보가 저장된 위치와 관계없이 모든 민감한 정보를 암호화하는 것이 좋습니다.
전체 디스크 암호화(또는 Android 10의 파일 기반 암호화)는 물리적 액세스 및 기타 공격 벡터로부터 데이터를 보호하기 위한 조치입니다. 따라서 동일한 보안 조치를 허용하기 위해
외부 저장소에 보관된 데이터는 Google에서 추가적으로 암호화해야
애플리케이션입니다.
데이터나 코드를 외부 저장소에서 애플리케이션으로 로드해야 하는 경우 다른 애플리케이션에서 이 데이터나 코드를 조작하지 않았는지 확인하는 무결성 검사를 실행하는 것이 좋습니다. 파일의 해시는 안전한 방식으로 저장해야 하며, 가능하면 암호화하여 내부 저장소에 저장해야 합니다.
Kotlin
package com.example.myapplication
import java.io.BufferedInputStream
import java.io.FileInputStream
import java.io.IOException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
object FileIntegrityChecker {
@Throws(IOException::class, NoSuchAlgorithmException::class)
fun getIntegrityHash(filePath: String?): String {
val md = MessageDigest.getInstance("SHA-256") // You can choose other algorithms as needed
val buffer = ByteArray(8192)
var bytesRead: Int
BufferedInputStream(FileInputStream(filePath)).use { fis ->
while (fis.read(buffer).also { bytesRead = it } != -1) {
md.update(buffer, 0, bytesRead)
}
}
private fun bytesToHex(bytes: ByteArray): String {
val sb = StringBuilder()
for (b in bytes) {
sb.append(String.format("%02x", b))
}
return sb.toString()
}
@Throws(IOException::class, NoSuchAlgorithmException::class)
fun verifyIntegrity(filePath: String?, expectedHash: String): Boolean {
val actualHash = getIntegrityHash(filePath)
return actualHash == expectedHash
}
@Throws(Exception::class)
@JvmStatic
fun main(args: Array<String>) {
val filePath = "/path/to/your/file"
val expectedHash = "your_expected_hash_value"
if (verifyIntegrity(filePath, expectedHash)) {
println("File integrity is valid!")
} else {
println("File integrity is compromised!")
}
}
}
Java
package com.example.myapplication;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class FileIntegrityChecker {
public static String getIntegrityHash(String filePath) throws IOException, NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256"); // You can choose other algorithms as needed
byte[] buffer = new byte[8192];
int bytesRead;
try (BufferedInputStream fis = new BufferedInputStream(new FileInputStream(filePath))) {
while ((bytesRead = fis.read(buffer)) != -1) {
md.update(buffer, 0, bytesRead);
}
}
byte[] digest = md.digest();
return bytesToHex(digest);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public static boolean verifyIntegrity(String filePath, String expectedHash) throws IOException, NoSuchAlgorithmException {
String actualHash = getIntegrityHash(filePath);
return actualHash.equals(expectedHash);
}
public static void main(String[] args) throws Exception {
String filePath = "/path/to/your/file";
String expectedHash = "your_expected_hash_value";
if (verifyIntegrity(filePath, expectedHash)) {
System.out.println("File integrity is valid!");
} else {
System.out.println("File integrity is compromised!");
}
}
}
리소스
이 페이지에 나와 있는 콘텐츠와 코드 샘플에는 콘텐츠 라이선스에서 설명하는 라이선스가 적용됩니다. 자바 및 OpenJDK는 Oracle 및 Oracle 계열사의 상표 또는 등록 상표입니다.
최종 업데이트: 2025-08-04(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-08-04(UTC)"],[],[],null,["# Sensitive Data Stored in External Storage\n\n\u003cbr /\u003e\n\n**OWASP category:** [MASVS-STORAGE: Storage](https://mas.owasp.org/MASVS/05-MASVS-STORAGE)\n\nOverview\n--------\n\nApplications targeting Android 10 (API 29) or lower don't enforce [scoped\nstorage](/training/data-storage#scoped-storage). This means that any data stored on the external storage can be\naccessed by any other application with the [`READ_EXTERNAL_STORAGE`](/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE)\npermission.\n\nImpact\n------\n\nIn applications targeting Android 10 (API 29) or lower, if sensitive data is\nstored on the external storage, any application on the device with the\nREAD_EXTERNAL_STORAGE permission can access it. This allows malicious\napplications to silently access sensitive files permanently or temporarily\nstored on the external storage. Additionally, since content on the external\nstorage can be accessed by any app on the system, any malicious application that\nalso declares the WRITE_EXTERNAL_STORAGE permission can tamper with files stored\non the external storage, e.g. to include malicious data. This malicious\ndata, if loaded into the application, could be designed to deceive users or even\nachieve code execution.\n\nMitigations\n-----------\n\n### Scoped Storage (Android 10 and later)\n\n##### Android 10\n\nFor applications targeting Android 10, developers can explicitly opt-in to\nscoped storage. This can be achieved by setting the\n[`requestLegacyExternalStorage`](/reference/android/R.attr#requestLegacyExternalStorage) flag to **false** in the\n`AndroidManifest.xml` file. With scoped storage, applications can only access\nfiles that they have created themselves on the external storage or files types\nthat were stored using the [MediaStore API](/reference/android/provider/MediaStore) such as Audio and Video. This\nhelps protect user privacy and security.\n\n##### Android 11 and later\n\nFor applications targeting Android 11 or later versions, the OS [enforces the\nuse of scoped storage](/about/versions/11/privacy/storage#scoped-storage), i.e. it ignores the\n[`requestLegacyExternalStorage`](/reference/android/R.attr#requestLegacyExternalStorage) flag and automatically protects\napplications' external storage from unwanted access.\n\n### Use Internal Storage for Sensitive Data\n\nRegardless of the targeted Android version, an application's sensitive data\nshould always be stored on internal storage. Access to internal storage is\nautomatically restricted to the owning application thanks to Android sandboxing,\ntherefore it can be considered secure, unless the device is rooted.\n\n### Encrypt sensitive data\n\nIf the application's use cases require storing sensitive data on the external\nstorage, the data should be encrypted. A strong encryption algorithm is\nrecommended, using the [Android KeyStore](/privacy-and-security/keystore) to safely store the key.\n\nIn general, encrypting all sensitive data is a recommended security practice, no\nmatter where it is stored.\n\nIt is important to note that full disk encryption (or file-based encryption from\nAndroid 10) is a measure aimed at protecting data from physical access and other\nattack vectors. Because of this, to grant the same security measure, sensitive\ndata held on external storage should additionally be encrypted by the\napplication.\n\n### Perform integrity checks\n\nIn cases where data or code has to be loaded from the external storage into the\napplication, integrity checks to verify that no other application has tampered\nwith this data or code are recommended. The hashes of the files should be stored\nin a secure manner, preferably encrypted and in the internal storage. \n\n### Kotlin\n\n package com.example.myapplication\n\n import java.io.BufferedInputStream\n import java.io.FileInputStream\n import java.io.IOException\n import java.security.MessageDigest\n import java.security.NoSuchAlgorithmException\n\n object FileIntegrityChecker {\n @Throws(IOException::class, NoSuchAlgorithmException::class)\n fun getIntegrityHash(filePath: String?): String {\n val md = MessageDigest.getInstance(\"SHA-256\") // You can choose other algorithms as needed\n val buffer = ByteArray(8192)\n var bytesRead: Int\n BufferedInputStream(FileInputStream(filePath)).use { fis -\u003e\n while (fis.read(buffer).also { bytesRead = it } != -1) {\n md.update(buffer, 0, bytesRead)\n }\n\n }\n\n private fun bytesToHex(bytes: ByteArray): String {\n val sb = StringBuilder()\n for (b in bytes) {\n sb.append(String.format(\"%02x\", b))\n }\n return sb.toString()\n }\n\n @Throws(IOException::class, NoSuchAlgorithmException::class)\n fun verifyIntegrity(filePath: String?, expectedHash: String): Boolean {\n val actualHash = getIntegrityHash(filePath)\n return actualHash == expectedHash\n }\n\n @Throws(Exception::class)\n @JvmStatic\n fun main(args: Array\u003cString\u003e) {\n val filePath = \"/path/to/your/file\"\n val expectedHash = \"your_expected_hash_value\"\n if (verifyIntegrity(filePath, expectedHash)) {\n println(\"File integrity is valid!\")\n } else {\n println(\"File integrity is compromised!\")\n }\n }\n }\n\n### Java\n\n package com.example.myapplication;\n\n import java.io.BufferedInputStream;\n import java.io.FileInputStream;\n import java.io.IOException;\n import java.security.MessageDigest;\n import java.security.NoSuchAlgorithmException;\n\n public class FileIntegrityChecker {\n\n public static String getIntegrityHash(String filePath) throws IOException, NoSuchAlgorithmException {\n MessageDigest md = MessageDigest.getInstance(\"SHA-256\"); // You can choose other algorithms as needed\n byte[] buffer = new byte[8192];\n int bytesRead;\n\n try (BufferedInputStream fis = new BufferedInputStream(new FileInputStream(filePath))) {\n while ((bytesRead = fis.read(buffer)) != -1) {\n md.update(buffer, 0, bytesRead);\n }\n }\n\n byte[] digest = md.digest();\n return bytesToHex(digest);\n }\n\n private static String bytesToHex(byte[] bytes) {\n StringBuilder sb = new StringBuilder();\n for (byte b : bytes) {\n sb.append(String.format(\"%02x\", b));\n }\n return sb.toString();\n }\n\n public static boolean verifyIntegrity(String filePath, String expectedHash) throws IOException, NoSuchAlgorithmException {\n String actualHash = getIntegrityHash(filePath);\n return actualHash.equals(expectedHash);\n }\n\n public static void main(String[] args) throws Exception {\n String filePath = \"/path/to/your/file\";\n String expectedHash = \"your_expected_hash_value\";\n\n if (verifyIntegrity(filePath, expectedHash)) {\n System.out.println(\"File integrity is valid!\");\n } else {\n System.out.println(\"File integrity is compromised!\");\n }\n }\n }\n\nResources\n---------\n\n- [Scoped storage](/training/data-storage#scoped-storage)\n- [READ_EXTERNAL_STORAGE](/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE)\n- [WRITE_EXTERNAL_STORAGE](/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE)\n- [requestLegacyExternalStorage](/reference/android/R.attr#requestLegacyExternalStorage)\n- [Data and file storage overview](/training/data-storage)\n- [Data Storage (App Specific)](/training/data-storage/app-specific)\n- [Cryptography](/privacy-and-security/cryptography)\n- [Keystore](/privacy-and-security/keystore)\n- [File-Based encryption](https://source.android.com/docs/security/features/encryption/file-based)\n- [Full-Disk encryption](https://source.android.com/docs/security/features/encryption/full-disk)"]]