Données sensibles stockées dans un espace de stockage externe

Catégorie OWASP : MASVS-STORAGE : stockage

Présentation

Les applications ciblant Android 10 (API 29) ou une version antérieure n'appliquent pas de champ d'application stockage. Cela signifie que toutes les données stockées sur la mémoire de stockage externe accessibles par toute autre application avec le READ_EXTERNAL_STORAGE l'autorisation.

Impact

Dans les applications ciblant Android 10 (API 29) ou une version antérieure, si les données sensibles sont stockées sur la mémoire de stockage externe, toute application installée sur l'appareil L'autorisation READ_EXTERNAL_STORAGE peut y accéder. Cela permet aux applications malveillantes d'accéder de manière silencieuse aux fichiers sensibles stockés de manière permanente ou temporaire sur le stockage externe. De plus, étant donné que le contenu le stockage est accessible par n'importe quelle application sur le système, toute application malveillante qui déclare également que l'autorisation WRITE_EXTERNAL_STORAGE peut modifier les fichiers stockés sur la mémoire de stockage externe, par exemple pour inclure des données malveillantes. Ce logiciel malveillant des données, si elles sont chargées dans l'application, peuvent être conçues pour tromper les utilisateurs, exécuter le code.

Stratégies d'atténuation

Espace de stockage cloisonné (Android 10 et versions ultérieures)

Android 10

Pour les applications ciblant Android 10, les développeurs peuvent activer explicitement le stockage avec portée. Pour ce faire, définissez l'option requestLegacyExternalStorage sur false dans le AndroidManifest.xml. Avec l'espace de stockage cloisonné, les applications ne peuvent accéder qu'aux fichiers qu'elles ont eux-mêmes créés sur le stockage externe ou aux types de fichiers stockés à l'aide de l'API MediaStore, tels que l'audio et la vidéo. Ce contribue à protéger la confidentialité et la sécurité des utilisateurs.

Android 11 et versions ultérieures

Pour les applications ciblant Android 11 ou version ultérieure, l'OS applique l'utilisation de l'espace de stockage cloisonné, c'est-à-dire qu'il ignore l'indicateur requestLegacyExternalStorage et protège automatiquement le stockage externe des applications contre tout accès indésirable.

Utiliser le stockage interne pour les données sensibles

Quelle que soit la version Android ciblée, les données sensibles d'une application doivent toujours être stockées dans la mémoire de stockage interne. L'accès à la mémoire de stockage interne est automatiquement limité à l'application propriétaire grâce au bac à sable Android, Il peut donc être considéré comme sécurisé, sauf si l'appareil est en mode root.

Chiffrer les données sensibles

Si les cas d'utilisation de l'application nécessitent de stocker des données sensibles sur le stockage externe, les données doivent être chiffrées. Un algorithme de chiffrement sécurisé est recommandé, en utilisant Android Keystore pour stocker la clé de manière sécurisée.

En général, le chiffrement de toutes les données sensibles est une pratique de sécurité recommandée. quel que soit l'emplacement de stockage.

Il est important de noter que le chiffrement complet du disque (ou le chiffrement basé sur les fichiers Android 10) est une mesure visant à protéger les données contre tout accès physique et d'autres les vecteurs d'attaque. Par conséquent, pour appliquer la même mesure de sécurité, les données sensibles stockées sur un stockage externe doivent également être chiffrées par l'application.

Effectuer des vérifications de l'intégrité

Lorsqu'il est nécessaire de charger des données ou du code depuis la mémoire de stockage externe vers application, des contrôles d'intégrité pour vérifier qu'aucune autre application n'a été altérée avec ces données ou ce code. Les hachages des fichiers doivent être stockés de manière sécurisée, de préférence chiffrés et dans la mémoire de stockage interne.

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

Ressources