داده‌های حساس در فضای ذخیره‌سازی خارجی ذخیره شده است

دسته OWASP: MASVS-STORAGE: Storage

نمای کلی

برنامه‌هایی که Android 10 (میانای برنامه‌سازی کاربردی ۲۹) یا پایین‌تر را هدف قرار می‌دهند، فضای ذخیره‌سازی محدود را اعمال نمی‌کنند. این یعنی هر داده‌ای که در فضای ذخیره‌سازی خارجی ذخیره شده باشد، توسط هر برنامه دیگری که اجازه READ_EXTERNAL_STORAGE را داشته باشد قابل‌دسترسی است.

تأثیر

در برنامه‌هایی که Android 10 (میانای برنامه‌سازی کاربردی ۲۹) یا پایین‌تر را هدف قرار می‌دهند، اگر داده‌های حساس در فضای ذخیره‌سازی خارجی ذخیره شده باشد، هر برنامه‌ای در دستگاه که اجازه READ_EXTERNAL_STORAGE را داشته باشد می‌تواند به آن دسترسی پیدا کند. این امر به برنامه‌های مخرب اجازه می‌دهد به‌طور پنهانی به فایل‌های حساسی که به‌طور دائم یا موقت در فضای ذخیره‌سازی خارجی ذخیره شده‌اند دسترسی پیدا کنند. علاوه‌براین، چون هر برنامه‌ای در سیستم می‌تواند به محتوای موجود در فضای ذخیره‌سازی خارجی دسترسی داشته باشد، هر برنامه مخربی که اجازه WRITE_EXTERNAL_STORAGE را نیز اعلام کند می‌تواند فایل‌های ذخیره‌شده در فضای ذخیره‌سازی خارجی را دستکاری کند، مثلاً داده‌های مخرب را در آن بگنجاند. این داده‌های مخرب، درصورت بار شدن در برنامه، ممکن است برای فریب دادن کاربران یا حتی اجرای کد طراحی شده باشند.

کاهش

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

‫Android 10

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

Android نسخه ۱۱ و بالاتر

برای برنامه‌هایی که Android 11 یا نسخه‌های جدیدتر را هدف قرار می‌دهند، سیستم‌عامل استفاده از فضای ذخیره‌سازی محدودشده را اجباری می‌کند، یعنی پرچم requestLegacyExternalStorage را نادیده می‌گیرد و به‌طور خودکار از فضای ذخیره‌سازی خارجی برنامه‌ها دربرابر دسترسی ناخواسته محافظت می‌کند.

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

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

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

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

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

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

انجام بررسی‌های تمامیت

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

کاتلین

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

منابع