داده های حساس ذخیره شده در حافظه خارجی

رده OWASP: MASVS-STORAGE: ذخیره‌سازی

نمای کلی

برنامه‌هایی که اندروید ۱۰ (API 29) یا پایین‌تر را هدف قرار می‌دهند، ذخیره‌سازی محدود (scoped storage) را اجرا نمی‌کنند. این بدان معناست که هر داده‌ای که در حافظه خارجی ذخیره شود، می‌تواند توسط هر برنامه دیگری با مجوز READ_EXTERNAL_STORAGE قابل دسترسی باشد.

تأثیر

در برنامه‌هایی که اندروید ۱۰ (API 29) یا پایین‌تر را هدف قرار می‌دهند، اگر داده‌های حساس در حافظه خارجی ذخیره شوند، هر برنامه‌ای روی دستگاه که مجوز READ_EXTERNAL_STORAGE داشته باشد می‌تواند به آن دسترسی پیدا کند. این امر به برنامه‌های مخرب اجازه می‌دهد تا به طور مخفیانه به فایل‌های حساس ذخیره شده در حافظه خارجی به طور دائم یا موقت دسترسی پیدا کنند. علاوه بر این، از آنجایی که محتوای حافظه خارجی توسط هر برنامه‌ای روی سیستم قابل دسترسی است، هر برنامه مخربی که مجوز WRITE_EXTERNAL_STORAGE را نیز اعلام کند، می‌تواند فایل‌های ذخیره شده در حافظه خارجی را دستکاری کند، مثلاً داده‌های مخرب را در آنها قرار دهد. این داده‌های مخرب، در صورت بارگذاری در برنامه، می‌توانند برای فریب کاربران یا حتی اجرای کد طراحی شوند.

کاهش‌ها

فضای ذخیره‌سازی محدود (اندروید ۱۰ و بالاتر)

اندروید ۱۰

برای برنامه‌هایی که اندروید ۱۰ را هدف قرار می‌دهند، توسعه‌دهندگان می‌توانند صریحاً از ذخیره‌سازی محدود (scoped storage) استفاده کنند. این کار را می‌توان با تنظیم پرچم requestLegacyExternalStorage روی false در فایل AndroidManifest.xml انجام داد. با ذخیره‌سازی محدود، برنامه‌ها فقط می‌توانند به فایل‌هایی که خودشان در حافظه خارجی ایجاد کرده‌اند یا انواع فایل‌هایی که با استفاده از MediaStore API مانند Audio و Video ذخیره شده‌اند، دسترسی داشته باشند. این به محافظت از حریم خصوصی و امنیت کاربر کمک می‌کند.

اندروید ۱۱ و بالاتر

برای برنامه‌هایی که اندروید ۱۱ یا نسخه‌های بالاتر را هدف قرار می‌دهند، سیستم عامل استفاده از فضای ذخیره‌سازی محدود (scoped storage) را اجباری می‌کند ، یعنی پرچم requestLegacyExternalStorage را نادیده می‌گیرد و به طور خودکار از فضای ذخیره‌سازی خارجی برنامه‌ها در برابر دسترسی ناخواسته محافظت می‌کند.

استفاده از حافظه داخلی برای داده‌های حساس

صرف نظر از نسخه اندروید مورد نظر، داده‌های حساس یک برنامه همیشه باید در حافظه داخلی ذخیره شوند. به لطف سندباکسینگ اندروید، دسترسی به حافظه داخلی به طور خودکار به برنامه مالک محدود می‌شود، بنابراین می‌توان آن را امن دانست، مگر اینکه دستگاه روت شده باشد.

رمزگذاری داده‌های حساس

اگر موارد استفاده برنامه نیاز به ذخیره داده‌های حساس در حافظه خارجی دارد، داده‌ها باید رمزگذاری شوند. یک الگوریتم رمزگذاری قوی توصیه می‌شود، با استفاده از Android KeyStore برای ذخیره ایمن کلید.

به طور کلی، رمزگذاری تمام داده‌های حساس، صرف نظر از محل ذخیره آنها، یک اقدام امنیتی توصیه شده است.

لازم به ذکر است که رمزگذاری کامل دیسک (یا رمزگذاری مبتنی بر فایل از اندروید ۱۰) اقدامی با هدف محافظت از داده‌ها در برابر دسترسی فیزیکی و سایر مسیرهای حمله است. به همین دلیل، برای ارائه همان اقدام امنیتی، داده‌های حساس نگهداری شده در حافظه خارجی نیز باید توسط برنامه رمزگذاری شوند.

انجام بررسی‌های یکپارچگی

در مواردی که داده‌ها یا کدها باید از حافظه خارجی به برنامه بارگذاری شوند، بررسی یکپارچگی برای تأیید اینکه هیچ برنامه دیگری این داده‌ها یا کد را دستکاری نکرده است، توصیه می‌شود. هش‌های فایل‌ها باید به شیوه‌ای ایمن، ترجیحاً رمزگذاری شده و در حافظه داخلی ذخیره شوند.

کاتلین

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

جاوا

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

منابع