Categoria do OWASP: MASVS-STORAGE - Armazenamento
Visão geral
Os aplicativos destinados ao Android 10 (API 29) ou versões anteriores não aplicam o armazenamento com escopo. Isso significa que qualquer dado armazenado no armazenamento externo pode ser
acessado por qualquer outro aplicativo com a READ_EXTERNAL_STORAGE
permissão.
Impacto
Em aplicativos destinados ao Android 10 (API 29) ou versões anteriores, se dados sensíveis forem armazenados no armazenamento externo, qualquer aplicativo no dispositivo com a permissão READ_EXTERNAL_STORAGE poderá acessá-los. Isso permite que aplicativos maliciosos acessem arquivos sensíveis armazenados de forma permanente ou temporária no armazenamento externo. Além disso, como o conteúdo no armazenamento externo pode ser acessado por qualquer app no sistema, qualquer aplicativo malicioso que também declare a permissão WRITE_EXTERNAL_STORAGE pode adulterar arquivos armazenados no armazenamento externo, por exemplo, para incluir dados maliciosos. Esses dados maliciosos, se carregados no aplicativo, podem ser projetados para enganar os usuários ou até mesmo executar códigos.
Mitigações
Armazenamento com escopo (Android 10 e mais recentes)
Android 10
Para aplicativos destinados ao Android 10, os desenvolvedores podem ativar explicitamente o armazenamento com escopo. Isso pode ser feito definindo a
requestLegacyExternalStorage flag como false no
AndroidManifest.xml arquivo. Com o armazenamento com escopo, os aplicativos só podem acessar
arquivos que eles mesmos criaram no armazenamento externo ou tipos de arquivos
armazenados usando a API MediaStore, como áudio e vídeo. Isso ajuda a proteger a privacidade e a segurança do usuário.
Android 11 e mais recentes
Para aplicativos destinados ao Android 11 ou versões mais recentes, o SO aplica o
uso do armazenamento com escopo, ou seja, ele ignora a
requestLegacyExternalStorage flag e protege automaticamente o
armazenamento externo dos aplicativos contra acesso indesejado.
Usar o armazenamento interno para dados sensíveis
Independente da versão do Android de destino, os dados sensíveis de um aplicativo sempre precisam ser armazenados no armazenamento interno. O acesso ao armazenamento interno é restrito automaticamente ao aplicativo proprietário graças ao sandbox do Android. Portanto, ele pode ser considerado seguro, a menos que o dispositivo tenha acesso root.
Criptografar dados sensíveis
Se os casos de uso do aplicativo exigirem o armazenamento de dados sensíveis no armazenamento externo, os dados precisarão ser criptografados. Recomendamos um algoritmo de criptografia forte, usando o Android KeyStore para armazenar a chave com segurança.
Em geral, a criptografia de todos os dados sensíveis é uma prática de segurança recomendada, não importa onde eles estejam armazenados.
É importante observar que a criptografia de disco completo (ou a criptografia baseada em arquivos do Android 10) é uma medida destinada a proteger dados contra acesso físico e outros vetores de ataque. Por esse motivo, para conceder a mesma medida de segurança, os dados sensíveis mantidos no armazenamento externo também precisam ser criptografados pelo aplicativo.
Realizar verificações de integridade
Nos casos em que dados ou códigos precisam ser carregados do armazenamento externo para o aplicativo, recomendamos verificações de integridade para verificar se nenhum outro aplicativo adulterou esses dados ou códigos. Os hashes dos arquivos precisam ser armazenados de maneira segura, de preferência criptografados e no armazenamento 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
- Armazenamento com escopo
- READ_EXTERNAL_STORAGE
- WRITE_EXTERNAL_STORAGE
- requestLegacyExternalStorage
- Visão geral do armazenamento de dados e arquivos
- Armazenamento de dados (específico do app)
- Criptografia
- Keystore
- Criptografia baseada em arquivos
- Criptografia de disco completo