存储在外部存储器中的敏感数据

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 或更高版本的应用,操作系统强制使用作用域存储,即它会忽略requestLegacyExternalStorage标志并自动保护应用的外部存储免受意外访问。

对敏感数据使用内部存储

无论目标 Android 版本是什么,应用的敏感数据都应始终存储在内部存储器中。由于 Android 沙盒机制,对内部存储的访问会自动限制为拥有该应用的应用,因此可以认为它是安全的,除非设备已获得 root 权限。

加密敏感数据

如果应用的用例需要将敏感数据存储在外部存储器上,则应加密数据。建议使用强大的加密算法,并使用Android KeyStore安全地存储密钥。

一般来说,加密所有敏感数据是一种推荐的安全实践,无论其存储位置如何。

需要注意的是,全盘加密(或 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!");
        }
    }
}

资源