Dati sensibili archiviati in uno spazio di archiviazione esterno

Categoria OWASP: MASVS-STORAGE: Storage

Panoramica

Le applicazioni che hanno come target Android 10 (API 29) o versioni precedenti non applicano lo storage isolato. Ciò significa che qualsiasi dato memorizzato nella memoria esterna può essere accessibile da qualsiasi altra applicazione con l'autorizzazione READ_EXTERNAL_STORAGE.

Impatto

Nelle applicazioni destinate ad Android 10 (API 29) o versioni precedenti, se i dati sensibili sono memorizzati nella memoria esterna, qualsiasi applicazione sul dispositivo con l'autorizzazione READ_EXTERNAL_STORAGE può accedervi. Ciò consente alle applicazioni dannose di accedere silenziosamente a file sensibili archiviati in modo permanente o temporaneo nello spazio di archiviazione esterno. Inoltre, poiché i contenuti dell'archivio esterno sono accessibili a qualsiasi app sul sistema, qualsiasi applicazione dannosa che dichiara anche l'autorizzazione WRITE_EXTERNAL_STORAGE può manomettere i file archiviati nell'archivio esterno, ad esempio per includere dati dannosi. Questi dati dannosi, se caricati nell'applicazione, potrebbero essere progettati per ingannare gli utenti o persino per eseguire codice.

Mitigazioni

Spazio di archiviazione isolato (Android 10 e versioni successive)

Android 10

Per le applicazioni destinate ad Android 10, gli sviluppatori possono attivare esplicitamente lo spazio di archiviazione isolato. Puoi farlo impostando il flag requestLegacyExternalStorage su false nel file AndroidManifest.xml. Con lo spazio di archiviazione isolato, le applicazioni possono accedere solo ai file che hanno creato autonomamente sull'unità di archiviazione esterna o ai tipi di file che sono stati archiviati utilizzando l'API MediaStore, ad esempio audio e video. Ciò contribuisce a proteggere la privacy e la sicurezza degli utenti.

Android 11 e versioni successive

Per le applicazioni che hanno come target Android 11 o versioni successive, il sistema operativo impone l'utilizzo dell'archiviazione mirata, ovvero ignora il flag requestLegacyExternalStorage e protegge automaticamente lo spazio di archiviazione esterno delle applicazioni da accessi indesiderati.

Utilizzare l'archivio interno per i dati sensibili

Indipendentemente dalla versione di Android di destinazione, i dati sensibili di un'applicazione devono sempre essere archiviati nella memoria interna. L'accesso alla memoria interna è automaticamente limitato all'applicazione proprietaria grazie al sandboxing di Android, pertanto può essere considerato sicuro, a meno che il dispositivo non sia rooted.

Criptare i dati sensibili

Se i casi d'uso dell'applicazione richiedono la memorizzazione di dati sensibili nella memoria esterna, i dati devono essere criptati. È consigliato un algoritmo crittografico efficace, che utilizzi Android KeyStore per archiviare in modo sicuro la chiave.

In generale, la crittografia di tutti i dati sensibili è una pratica di sicurezza consigliata, indipendentemente da dove vengono archiviati.

È importante notare che la crittografia completa del disco (o la crittografia basata su file di Android 10) è una misura volta a proteggere i dati dall'accesso fisico e da altri vettori di attacco. Per questo motivo, per garantire la stessa misura di sicurezza, i dati sensibili archiviati su un dispositivo di archiviazione esterno devono essere criptati anche dall'applicazione.

Eseguire controlli di integrità

Nei casi in cui i dati o il codice devono essere caricati dalla memoria esterna nell'applicazione, sono consigliati controlli di integrità per verificare che nessun'altra applicazione abbia manomesso questi dati o questo codice. Gli hash dei file devono essere archiviati in modo sicuro, preferibilmente criptati e nella memoria interna.

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

Risorse