מידע אישי רגיש שמאוחסנים באחסון חיצוני

קטגוריה ב-OWASP: MASVS-STORAGE: אחסון

סקירה כללית

אפליקציות שמטרגטות את Android 10 (API 29) ומטה לא אוכפים היקף אחסון נוסף. המשמעות היא שכל הנתונים שמאוחסנים באחסון החיצוני יכולים כל אפליקציה אחרת ניגשת אליה באמצעות READ_EXTERNAL_STORAGE הרשאה.

השפעה

באפליקציות שמטרגטות ל-Android 10 (API 29) ומטה, אם באחסון החיצוני, כל אפליקציה במכשיר עם ההרשאה READ_EXTERNAL_STORAGE יכולה לגשת אליו. כך אפליקציות זדוניות יכולות לגשת בחשאי לקבצים רגישים שנשמרו באופן זמני או קבוע באחסון החיצוני. כמו כן, מכיוון שתוכן בפלטפורמה לכל אפליקציה במערכת, כל אפליקציה זדונית יכולה לגשת לאחסון שלה מצהירה גם שההרשאה WRITE_EXTERNAL_STORAGE יכולה לפגוע בקבצים המאוחסנים באחסון החיצוני, כדי לכלול נתונים זדוניים. אם הנתונים הזדוניים האלה נטענים באפליקציה, הם עלולים להטעות משתמשים או אפילו להריץ קוד.

פעולות מיטיגציה

אחסון מוגדר היקף (ב-Android 10 ואילך)

10 Android

במפתחי אפליקציות שמטרגטות את Android 10 יכולים להביע הסכמה מפורשת לשימוש באחסון מוגבל. אפשר לעשות זאת באמצעות הגדרת הסימון requestLegacyExternalStorage כ-false קובץ AndroidManifest.xml. באחסון בהיקף, אפליקציות יכולות לגשת רק קבצים שהם יצרו בעצמם באחסון החיצוני או בסוגי הקבצים שאוחסנו באמצעות MediaStore API, כמו Audio and Video. כך אפשר להגן על הפרטיות והאבטחה של המשתמשים.

Android 11 ואילך

באפליקציות שמטרגטות לגרסאות Android 11 ואילך, מערכת ההפעלה אוכפת בשימוש בהיקף אחסון, כלומר מתעלם דגל requestLegacyExternalStorage ומגן באופן אוטומטי אפליקציות אחסון חיצוני מגישה לא רצויה.

שימוש באחסון פנימי למידע רגיש

ללא קשר לגרסת Android המטורגטת, המידע האישי הרגיש של האפליקציה יש לאחסן תמיד באחסון פנימי. הגישה לאחסון הפנימי מוגבלת באופן אוטומטי לאפליקציה הבעלים, בזכות ארגז החול של Android. לכן, אפשר להחשיב אותה כאבטחה, אלא אם המכשיר עבר תהליך רוט (Root).

הצפנת מידע אישי רגיש

אם תרחישי השימוש של האפליקציה דורשים אחסון של מידע אישי רגיש באחסון החיצוני, הנתונים צריכים להיות מוצפנים. אלגוריתם הצפנה חזק להשתמש ב-Android KeyStore כדי לאחסן את המפתח בבטחה.

באופן כללי, מומלץ להצפין את כל המידע הרגיש, לא משנה איפה הוא מאוחסן.

חשוב לציין שהצפנת דיסק מלאה (או הצפנה מבוססת-קובץ מ-Android 10) היא אמצעי שנועד להגן על נתונים מפני גישה פיזית ומפני דרכים אחרות להתקפה. לכן, כדי להעניק את אותו אמצעי אבטחה, נתונים ששמורים באחסון חיצוני צריכים להיות מוצפנים בנוסף על ידי תרגום מכונה.

ביצוע בדיקות תקינות

במקרים שבהם צריך לטעון נתונים או קוד מהאחסון החיצוני לאפליקציה, מומלץ לבצע בדיקות תקינות כדי לוודא שאף אפליקציה אחרת לא פגעה בנתונים או בקוד האלה. צריך לשמור את הגיבובים של הקבצים באופן מאובטח, רצוי שיהיה מוצפן ובאחסון הפנימי.

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

משאבים