Categoría de OWASP: MASVS-STORAGE: Almacenamiento
Descripción general
Las aplicaciones orientadas a Android 10 (API 29) o versiones anteriores no aplican el almacenamiento específico. Esto significa que cualquier otra aplicación con el permiso READ_EXTERNAL_STORAGE puede acceder a los datos almacenados en el almacenamiento externo.
Impacto
En las aplicaciones orientadas a Android 10 (API 29) o versiones anteriores, si se almacenan datos sensibles en el almacenamiento externo, cualquier aplicación del dispositivo con el permiso READ_EXTERNAL_STORAGE puede acceder a ellos. Esto permite que las aplicaciones maliciosas accedan de forma silenciosa a archivos sensibles almacenados de forma permanente o temporal en el almacenamiento externo. Además, dado que cualquier app del sistema puede acceder al contenido del almacenamiento externo, cualquier aplicación maliciosa que también declare el permiso WRITE_EXTERNAL_STORAGE puede manipular los archivos almacenados en el almacenamiento externo, p.ej., para incluir datos maliciosos. Si se cargan estos datos maliciosos en la aplicación, podrían estar diseñados para engañar a los usuarios o incluso lograr la ejecución de código.
Mitigaciones
Almacenamiento específico (Android 10 y versiones posteriores)
Android 10
En el caso de las aplicaciones segmentadas para Android 10, los desarrolladores pueden habilitar explícitamente el almacenamiento específico. Para ello, configura la marca requestLegacyExternalStorage en false en el archivo AndroidManifest.xml. Con el almacenamiento específico, las aplicaciones solo pueden acceder a los archivos que crearon ellas mismas en el almacenamiento externo o a los tipos de archivos que se almacenaron con la API de MediaStore, como audio y video. Esto ayuda a proteger la privacidad y la seguridad de los usuarios.
Android 11 y versiones posteriores
En el caso de las aplicaciones que se segmentan para Android 11 o versiones posteriores, el SO aplica el uso del almacenamiento específico, es decir, ignora la marca requestLegacyExternalStorage y protege automáticamente el almacenamiento externo de las aplicaciones del acceso no deseado.
Usa el almacenamiento interno para los datos sensibles
Independientemente de la versión de Android para la que se diseñó, los datos sensibles de una aplicación siempre deben almacenarse en el almacenamiento interno. El acceso al almacenamiento interno se restringe automáticamente a la aplicación propietaria gracias a la zona de pruebas de Android, por lo que se puede considerar seguro, a menos que el dispositivo tenga acceso de administrador.
Encripta datos sensibles
Si los casos de uso de la aplicación requieren almacenar datos sensibles en el almacenamiento externo, los datos deben encriptarse. Se recomienda usar un algoritmo de encriptación sólido con Android Keystore para almacenar la clave de forma segura.
En general, encriptar todos los datos sensibles es una práctica de seguridad recomendada, sin importar dónde se almacenen.
Es importante tener en cuenta que la encriptación de disco completo (o la encriptación basada en archivos desde Android 10) es una medida que tiene como objetivo proteger los datos del acceso físico y otros vectores de ataque. Por este motivo, para otorgar la misma medida de seguridad, la aplicación también debe encriptar los datos sensibles que se encuentran en el almacenamiento externo.
Realiza verificaciones de integridad
En los casos en los que se deben cargar datos o código desde el almacenamiento externo a la aplicación, se recomiendan las verificaciones de integridad para comprobar que ninguna otra aplicación haya manipulado estos datos o código. Los hashes de los archivos deben almacenarse de forma segura, de preferencia encriptados y en el almacenamiento interno.
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!");
}
}
}
Recursos
- Almacenamiento específico
- READ_EXTERNAL_STORAGE
- WRITE_EXTERNAL_STORAGE
- requestLegacyExternalStorage
- Descripción general del almacenamiento de datos y archivos
- Almacenamiento de datos (específico de la app)
- Criptografía
- Almacén de claves
- Encriptación basada en archivos
- Encriptación de disco completo