Конфиденциальные данные, хранящиеся во внешнем хранилище

Категория OWASP: MASVS-STORAGE: Хранилище

Обзор

Приложения, предназначенные для Android 10 (API 29) или более ранней версии, не используют ограниченное хранилище . Это означает, что любые данные, хранящиеся во внешнем хранилище, могут быть доступны любому другому приложению с разрешением READ_EXTERNAL_STORAGE .

Влияние

В приложениях, предназначенных для Android 10 (API 29) или более ранней версии, если конфиденциальные данные хранятся во внешнем хранилище, любое приложение на устройстве с разрешением READ_EXTERNAL_STORAGE может получить к ним доступ. Это позволяет вредоносным приложениям бесшумно получать доступ к конфиденциальным файлам, постоянно или временно хранящимся на внешнем хранилище. Кроме того, поскольку к содержимому внешнего хранилища может получить доступ любое приложение в системе, любое вредоносное приложение, которое также заявляет разрешение WRITE_EXTERNAL_STORAGE, может искажать файлы, хранящиеся во внешнем хранилище, например, включать вредоносные данные. Эти вредоносные данные, если они загружены в приложение, могут быть предназначены для обмана пользователей или даже для выполнения кода.

Смягчения

Ограниченное хранилище (Android 10 и более поздние версии)

Андроид 10

Для приложений, ориентированных на Android 10, разработчики могут явно выбрать использование хранилища с ограниченной областью действия. Этого можно добиться, установив для флага requestLegacyExternalStorage значение false в файле AndroidManifest.xml . При использовании хранилища с ограниченной областью приложения могут получать доступ только к файлам, которые они создали сами во внешнем хранилище, или к типам файлов, которые были сохранены с использованием API MediaStore, например аудио и видео. Это помогает защитить конфиденциальность и безопасность пользователей.

Android 11 и более поздние версии

Для приложений, предназначенных для Android 11 или более поздних версий, операционная система принудительно использует ограниченное хранилище , т. е. игнорирует флаг requestLegacyExternalStorage и автоматически защищает внешнее хранилище приложений от нежелательного доступа.

Используйте внутреннее хранилище для конфиденциальных данных

Независимо от целевой версии Android, конфиденциальные данные приложения всегда должны храниться во внутренней памяти. Доступ к внутренней памяти автоматически ограничивается приложением-владельцем благодаря песочнице Android, поэтому его можно считать безопасным, если только устройство не рутировано.

Шифрование конфиденциальных данных

Если сценарии использования приложения требуют хранения конфиденциальных данных во внешнем хранилище, данные следует зашифровать. Рекомендуется использовать надежный алгоритм шифрования и использовать Android KeyStore для безопасного хранения ключа.

В целом шифрование всех конфиденциальных данных является рекомендуемой мерой безопасности, независимо от того, где они хранятся.

Важно отметить, что полное шифрование диска (или файловое шифрование в Android 10) — это мера, направленная на защиту данных от физического доступа и других векторов атак. По этой причине для обеспечения такой же меры безопасности конфиденциальные данные, хранящиеся на внешнем хранилище, должны дополнительно шифроваться приложением.

Выполните проверку целостности

В тех случаях, когда данные или код необходимо загрузить из внешнего хранилища в приложение, рекомендуется выполнить проверку целостности, чтобы убедиться, что никакое другое приложение не подделало эти данные или код. Хэши файлов должны храниться безопасным образом, желательно в зашифрованном виде, во внутреннем хранилище.

Котлин

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!")
        }
    }
}

Ява

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!");
        }
    }
}

Ресурсы