Catégorie OWASP : MASVS-STORAGE : stockage
Présentation
Les applications ciblant Android 10 (API 29) ou version antérieure n'appliquent pas l'espace de stockage cloisonné. Cela signifie que toutes les données stockées sur le stockage externe sont accessibles par n'importe quelle autre application disposant de l'autorisation READ_EXTERNAL_STORAGE.
Impact
Dans les applications ciblant Android 10 (API 29) ou version antérieure, si des données sensibles sont stockées sur le stockage externe, toute application sur l'appareil disposant de l'autorisation READ_EXTERNAL_STORAGE peut y accéder. Cela permet aux applications malveillantes d'accéder silencieusement aux fichiers sensibles stockés de manière permanente ou temporaire sur le stockage externe. De plus, comme le contenu du stockage externe est accessible par n'importe quelle application du système, toute application malveillante qui déclare également l'autorisation WRITE_EXTERNAL_STORAGE peut falsifier les fichiers stockés sur le stockage externe, par exemple pour inclure des données malveillantes. Ces données malveillantes, si elles sont chargées dans l'application, peuvent être conçues pour tromper les utilisateurs ou même exécuter du 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 l'espace de stockage cloisonné. Pour ce faire, définissez l'indicateur requestLegacyExternalStorage sur false dans le fichier AndroidManifest.xml. Avec l'espace de stockage cloisonné, les applications ne peuvent accéder qu'aux fichiers qu'elles ont créés elles-mêmes sur le stockage externe ou aux types de fichiers stockés à l'aide de l'API MediaStore, tels que les fichiers audio et vidéo. Cela permet de protéger la confidentialité et la sécurité des utilisateurs.
Android 11 et versions ultérieures
Pour les applications ciblant Android 11 ou une version ultérieure, l'OS impose 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 les accès indésirables.
Utiliser le stockage interne pour les données sensibles
Quelle que soit la version d'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 rooté.
Chiffrer les données sensibles
Si les cas d'utilisation de l'application nécessitent le stockage de données sensibles sur la mémoire externe, les données doivent être chiffrées. Il est recommandé d'utiliser un algorithme de chiffrement fort et de stocker la clé de manière sécurisée à l'aide d'Android KeyStore.
En général, le chiffrement de toutes les données sensibles est une pratique de sécurité recommandée, quel que soit l'endroit où elles sont stockées.
Il est important de noter que le chiffrement complet du disque (ou le chiffrement basé sur les fichiers à partir d'Android 10) est une mesure visant à protéger les données contre l'accès physique et d'autres vecteurs d'attaque. Par conséquent, pour garantir le même niveau de sécurité, les données sensibles stockées sur un support externe doivent également être chiffrées par l'application.
Effectuer des vérifications de l'intégrité
Dans les cas où des données ou du code doivent être chargés depuis l'espace de stockage externe dans l'application, il est recommandé d'effectuer des contrôles d'intégrité pour vérifier qu'aucune autre application n'a falsifié 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
- Espace de stockage cloisonné
- READ_EXTERNAL_STORAGE
- WRITE_EXTERNAL_STORAGE
- requestLegacyExternalStorage
- Présentation du stockage des données et des fichiers
- Stockage des données (spécifique à l'application)
- Cryptographie
- Keystore
- Chiffrement basé sur les fichiers
- Chiffrement complet du disque