Sensible Daten, die in einem externen Speicher gespeichert sind

OWASP-Kategorie:MASVS-STORAGE: Storage

Übersicht

Bei Anwendungen, die auf Android 10 (API 29) oder niedriger ausgerichtet sind, wird Scoped Storage nicht erzwungen. Das bedeutet, dass jede andere Anwendung mit der Berechtigung READ_EXTERNAL_STORAGE auf alle Daten zugreifen kann, die auf dem externen Speicher gespeichert sind.

Auswirkungen

Wenn in Anwendungen, die auf Android 10 (API 29) oder niedriger ausgerichtet sind, vertrauliche Daten im externen Speicher gespeichert werden, kann jede Anwendung auf dem Gerät mit der Berechtigung READ_EXTERNAL_STORAGE darauf zugreifen. Dadurch können schädliche Anwendungen dauerhaft oder vorübergehend auf sensible Dateien zugreifen, die auf dem externen Speicher gespeichert sind. Da außerdem jede App auf dem System auf Inhalte auf dem externen Speicher zugreifen kann, kann jede schädliche Anwendung, die auch die Berechtigung WRITE_EXTERNAL_STORAGE deklariert, Dateien auf dem externen Speicher manipulieren, z.B. um schädliche Daten einzufügen. Diese schädlichen Daten können, wenn sie in die Anwendung geladen werden, darauf ausgelegt sein, Nutzer zu täuschen oder sogar die Ausführung von Code zu ermöglichen.

Gegenmaßnahmen

Scoped Storage (Android 10 und höher)

Android 10

Für Anwendungen, die auf Android 10 ausgerichtet sind, können Entwickler explizit die Verwendung von begrenztem Speicher aktivieren. Dazu muss das Flag requestLegacyExternalStorage in der Datei AndroidManifest.xml auf false gesetzt werden. Mit dem begrenzten Speicher können Anwendungen nur auf Dateien zugreifen, die sie selbst auf dem externen Speicher erstellt haben, oder auf Dateitypen, die mit der MediaStore API gespeichert wurden, z. B. Audio- und Videodateien. Das trägt zum Schutz der Privatsphäre und Sicherheit der Nutzer bei.

Android 11 und höher

Bei Apps, die auf Android 11 oder höher ausgerichtet sind, erzwingt das Betriebssystem die Verwendung von scoped storage. Das bedeutet, dass das Flag requestLegacyExternalStorage ignoriert wird und der externe Speicher von Apps automatisch vor unerwünschtem Zugriff geschützt wird.

Internen Speicher für sensible Daten verwenden

Unabhängig von der Ziel-Android-Version sollten vertrauliche Daten einer Anwendung immer im internen Speicher gespeichert werden. Der Zugriff auf den internen Speicher ist dank der Android-Sandbox automatisch auf die zugehörige Anwendung beschränkt. Er kann daher als sicher betrachtet werden, sofern das Gerät nicht gerootet ist.

Vertrauliche Daten verschlüsseln

Wenn für die Anwendungsfälle der App das Speichern vertraulicher Daten im externen Speicher erforderlich ist, sollten die Daten verschlüsselt werden. Es wird ein starker Verschlüsselungsalgorithmus empfohlen, bei dem der Schlüssel sicher im Android-Schlüsselspeicher gespeichert wird.

Im Allgemeinen ist die Verschlüsselung aller vertraulichen Daten eine empfohlene Sicherheitsmaßnahme, unabhängig davon, wo sie gespeichert werden.

Die Datenträgervollverschlüsselung (oder die dateibasierte Verschlüsselung ab Android 10) ist eine Maßnahme, die darauf abzielt, Daten vor physischem Zugriff und anderen Angriffsvektoren zu schützen. Aus diesem Grund sollten sensible Daten, die auf externem Speicher gespeichert sind, zusätzlich von der Anwendung verschlüsselt werden, um dieselbe Sicherheitsmaßnahme zu gewährleisten.

Integritätsprüfungen durchführen

Wenn Daten oder Code aus dem externen Speicher in die Anwendung geladen werden müssen, sind Integritätsprüfungen empfehlenswert, um zu überprüfen, ob keine andere Anwendung diese Daten oder diesen Code manipuliert hat. Die Hashes der Dateien sollten sicher gespeichert werden, vorzugsweise verschlüsselt und im internen Speicher.

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

Ressourcen