Data Sensitif Disimpan di Penyimpanan Eksternal

Kategori OWASP: MASVS-STORAGE: Storage

Ringkasan

Aplikasi yang menargetkan Android 10 (API 29) atau yang lebih rendah tidak menerapkan cakupan Google Cloud Storage. Ini berarti bahwa setiap data yang disimpan di penyimpanan eksternal dapat diakses oleh aplikasi lain dengan READ_EXTERNAL_STORAGE izin akses.

Dampak

Dalam aplikasi yang menargetkan Android 10 (API 29) atau yang lebih rendah, jika data sensitif disimpan di penyimpanan eksternal, aplikasi apa pun di perangkat dengan izin READ_EXTERNAL_STORAGE dapat mengaksesnya. Hal ini memungkinkan serangan aplikasi untuk mengakses file sensitif secara otomatis atau sementara yang disimpan di penyimpanan eksternal. Selain itu, karena konten eksternal penyimpanan dapat diakses oleh aplikasi apa pun pada sistem, aplikasi berbahaya apa pun yang juga mendeklarasikan izin WRITE_EXTERNAL_STORAGE dapat mengutak-atik file yang disimpan di penyimpanan eksternal, misalnya untuk menyertakan data berbahaya. Data berbahaya ini, jika dimuat ke dalam aplikasi, dapat dirancang untuk menipu pengguna atau bahkan mencapai eksekusi kode.

Mitigasi

Penyimpanan Terbatas (Android 10 dan yang lebih baru)

Android 10

Untuk aplikasi yang menargetkan Android 10, developer dapat secara eksplisit memilih ikut serta dalam dengan penyimpanan terbatas. Hal ini dapat dilakukan dengan menyetel atribut requestLegacyExternalStorage ditandai ke false di AndroidManifest.xml. Dengan penyimpanan terbatas, aplikasi hanya dapat mengakses file yang mereka buat sendiri di penyimpanan eksternal atau jenis file yang disimpan menggunakan MediaStore API seperti Audio dan Video. Ini membantu melindungi privasi dan keamanan pengguna.

Android 11 dan yang lebih baru

Untuk aplikasi yang menargetkan Android 11 atau versi yang lebih baru, OS menerapkan penggunaan penyimpanan terbatas, yaitu mengabaikan requestLegacyExternalStorage menandai dan otomatis melindungi aplikasi penyimpanan eksternal dari akses yang tidak diinginkan.

Menggunakan Penyimpanan Internal untuk Data Sensitif

Apa pun versi Android yang ditargetkan, data sensitif aplikasi harus selalu disimpan di penyimpanan internal. Akses ke penyimpanan internal secara otomatis dibatasi hanya untuk aplikasi yang dimiliki berkat sandbox Android, oleh karena itu, dianggap aman, kecuali jika perangkat di-root.

Mengenkripsi data sensitif

Jika kasus penggunaan aplikasi memerlukan penyimpanan data sensitif di server eksternal penyimpanan, data itu harus dienkripsi. Algoritma enkripsi yang kuat direkomendasikan, menggunakan Android KeyStore untuk menyimpan kunci dengan aman.

Secara umum, mengenkripsi semua data sensitif adalah praktik keamanan yang disarankan, peduli di mana data tersebut disimpan.

Penting untuk diperhatikan bahwa enkripsi disk penuh (atau enkripsi berbasis file dari Android 10) adalah tindakan yang bertujuan melindungi data dari akses fisik dan vektor serangan lainnya. Karena itu, untuk memberikan langkah keamanan yang sama, data yang disimpan di penyimpanan eksternal juga harus dienkripsi oleh aplikasi.

Melakukan pemeriksaan integritas

Jika data atau kode harus dimuat dari penyimpanan eksternal ke aplikasi, pemeriksaan integritas untuk memverifikasi bahwa tidak ada aplikasi lain yang dirusak dengan data atau kode ini adalah hal yang direkomendasikan. Hash file harus disimpan dengan cara yang aman, sebaiknya dienkripsi dan dalam penyimpanan internal.

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

Referensi