Categoria do OWASP: MASVS-STORAGE - Armazenamento (link em inglês)
Visão geral
Apps destinados ao Android 10 (API 29) ou versões anteriores não impõem escopo
Storage. Isso significa que todos os dados armazenados no armazenamento externo podem ser
acessados por qualquer outro aplicativo com a permissão
READ_EXTERNAL_STORAGE
.
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 silenciosamente arquivos sensíveis armazenados permanentemente ou temporariamente 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. Um usuário dados carregados no aplicativo podem ser projetados para enganar usuários ou mesmo para executar o código.
Mitigações
Armazenamento com escopo (Android 10 e versões mais recentes)
Android 10
Para aplicativos destinados ao Android 10, os desenvolvedores podem optar explicitamente por:
armazenamento com escopo. Isso pode ser feito definindo o
a sinalização requestLegacyExternalStorage
como false na
AndroidManifest.xml
. Com o armazenamento com escopo, os aplicativos só podem acessar
os arquivos que eles mesmos criaram no armazenamento externo ou os tipos de arquivos
que foram 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 direcionados ao Android 11 ou versões mais recentes, o SO impõe o
uso do armazenamento com escopo, ou seja, ele ignora a
flag requestLegacyExternalStorage
e protege automaticamente
o armazenamento externo dos aplicativos contra acessos indesejados.
Usar armazenamento interno para dados confidenciais
Independentemente da versão do Android, os dados sensíveis de um app sempre precisam ser armazenados no armazenamento interno. O acesso ao armazenamento interno é são automaticamente restritas 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 precisam ser criptografados. É recomendado usar um algoritmo de criptografia forte para armazenar a chave com segurança com o Android KeyStore.
Em geral, criptografar todos os dados sensíveis é uma prática de segurança recomendada, sem onde quer que estejam armazenados.
É importante observar que a criptografia de disco completo (ou a criptografia baseada em arquivo Android 10) é uma medida destinada a proteger os dados contra acesso físico e outras vetores de ataque. Por isso, para conceder a mesma medida de segurança, os dados sensíveis armazenados no armazenamento externo também precisam ser criptografados pelo aplicativo.
Realizar verificações de integridade
Nos casos em que é necessário carregar dados ou código do armazenamento externo para o do Google, verificações de integridade para verificar se nenhum outro aplicativo foi adulterado com esses dados ou código. Os hashes dos arquivos precisam ser armazenados de forma 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 para apps)
- Criptografia
- Keystore
- Criptografia baseada em arquivos
- Criptografia de disco completo