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 为目标平台的应用,开发者可以明确选择启用分区存储。为此,可将
将 requestLegacyExternalStorage
标志设置为 false
AndroidManifest.xml
文件。借助分区存储,应用只能访问自己在外部存储设备上创建的文件,或使用 MediaStore API 存储的文件类型(例如音频和视频)。这有助于保护用户隐私和安全。
Android 11 及更高版本
对于以 Android 11 或更高版本为目标平台的应用,操作系统会强制使用分区存储,即会忽略 requestLegacyExternalStorage
标志,并自动保护应用的外部存储空间免遭不必要的访问。
将内部存储空间用于敏感数据
无论目标 Android 版本如何,应用的敏感数据都应始终存储在内部存储空间中。对内部存储空间的访问权限为 得益于 Android 沙盒, 因此,除非设备已启用 root 权限,否则会被视为安全。
加密敏感数据
如果应用的用例要求将敏感数据存储在外部 数据,应进行加密。强加密算法是指 建议使用 Android 密钥库来安全存储密钥。
一般来说,建议对所有敏感数据进行加密,无论这些数据存储在何处。
请务必注意,全盘加密 Android 10)是一项旨在保护数据免遭物理访问和其他 攻击途径。因此,为了提供相同的安全措施,应用还应对存储在外部存储设备上的敏感数据进行加密。
执行完整性检查
在需要将数据从外部存储空间加载到 应用、完整性检查以确认没有其他应用被篡改 包含这些数据或代码。文件的哈希值应以安全的方式存储,最好是加密并存储在内部存储空间中。
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!");
}
}
}
资源
- 分区存储
- READ_EXTERNAL_STORAGE
- WRITE_EXTERNAL_STORAGE
- requestLegacyExternalStorage
- 数据和文件存储概览
- 数据存储(特定于应用)
- 加密
- 密钥库
- 文件级加密
- 全盘加密