Dane wrażliwe przechowywane w pamięci zewnętrznej

Kategoria OWASP: MASVS-STORAGE: Storage

Przegląd

Aplikacje kierowane na Androida 10 (API 29) lub starszego nie wymuszają korzystania z pamięci o ograniczonym zakresie. Oznacza to, że do wszystkich danych przechowywanych w pamięci zewnętrznej może uzyskać dostęp każda inna aplikacja z uprawnieniem READ_EXTERNAL_STORAGE.

Wpływ

Jeśli w aplikacjach kierowanych na Androida 10 (API 29) lub starszego dane wrażliwe są przechowywane w pamięci zewnętrznej, każda aplikacja na urządzeniu z uprawnieniem READ_EXTERNAL_STORAGE może uzyskać do nich dostęp. Umożliwia to złośliwym aplikacjom ciche uzyskiwanie dostępu do plików wrażliwych przechowywanych w pamięci zewnętrznej na stałe lub tymczasowo. Ponadto, ponieważ do treści w pamięci zewnętrznej może uzyskać dostęp każda aplikacja w systemie, każda złośliwa aplikacja, która deklaruje też uprawnienie WRITE_EXTERNAL_STORAGE, może manipulować plikami przechowywanymi w pamięci zewnętrznej, np. dodawać do nich złośliwe dane. Takie złośliwe dane, jeśli zostaną wczytane do aplikacji, mogą być zaprojektowane tak, aby wprowadzać użytkowników w błąd lub nawet wykonywać kod.

Środki zaradcze

Pamięć o ograniczonym zakresie (Android 10 i nowsze wersje)

Android 10

W przypadku aplikacji kierowanych na Androida 10 deweloperzy mogą wyraźnie włączyć korzystanie z ograniczonego dostępu do miejsca na dane. Można to zrobić, ustawiając w pliku AndroidManifest.xml flagę requestLegacyExternalStorage na false. W przypadku ograniczonego dostępu do miejsca na dane aplikacje mogą uzyskiwać dostęp tylko do plików, które same utworzyły w pamięci zewnętrznej, lub do typów plików przechowywanych za pomocą interfejsu MediaStore API, takich jak pliki audio i wideo. Pomaga to chronić prywatność i bezpieczeństwo użytkowników.

Android 11 i nowsze wersje

W przypadku aplikacji kierowanych na Androida 11 lub nowszego system operacyjny wymusza korzystanie z ograniczonego dostępu do miejsca na dane, czyli ignoruje requestLegacyExternalStorage flagę i automatycznie chroni pamięć zewnętrzną aplikacji przed niechcianym dostępem.

Używanie pamięci wewnętrznej do przechowywania danych wrażliwych

Niezależnie od wersji Androida, na którą jest kierowana aplikacja, dane wrażliwe powinny być zawsze przechowywane w pamięci wewnętrznej. Dostęp do pamięci wewnętrznej jest automatycznie ograniczony do aplikacji, która jest jej właścicielem, dzięki piaskownicy Androida. Dlatego można ją uznać za bezpieczną, chyba że urządzenie ma dostęp do roota.

Szyfrowanie danych wrażliwych

Jeśli przypadki użycia aplikacji wymagają przechowywania danych wrażliwych w pamięci zewnętrznej, dane te powinny być zaszyfrowane. Zalecamy używanie silnego algorytmu szyfrowania i bezpieczne przechowywanie klucza w magazynie kluczy Androida.

Ogólnie rzecz biorąc, szyfrowanie wszystkich danych wrażliwych jest zalecaną praktyką w zakresie bezpieczeństwa, niezależnie od tego, gdzie są one przechowywane.

Należy pamiętać, że pełne szyfrowanie dysku (lub szyfrowanie oparte na plikach od Androida 10) to środek mający na celu ochronę danych przed dostępem fizycznym i innymi wektorami ataku. Z tego powodu, aby zapewnić ten sam poziom bezpieczeństwa, dane wrażliwe przechowywane w pamięci zewnętrznej powinny być dodatkowo szyfrowane przez aplikację.

Sprawdzanie integralności

W przypadkach, gdy dane lub kod muszą być wczytywane z pamięci zewnętrznej do aplikacji, zalecamy sprawdzanie integralności, aby upewnić się, że żadna inna aplikacja nie manipulowała tymi danymi ani kodem. Hasze plików powinny być przechowywane w bezpieczny sposób, najlepiej zaszyfrowane i w pamięci wewnętrznej.

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

Zasoby