ข้อมูลที่ละเอียดอ่อนซึ่งจัดเก็บไว้ในที่จัดเก็บข้อมูลภายนอก

หมวดหมู่ OWASP: MASVS-STORAGE: Storage

ภาพรวม

แอปพลิเคชันที่กำหนดเป้าหมายเป็น Android 10 (API 29) หรือต่ำกว่าจะไม่ได้บังคับใช้ พื้นที่เก็บข้อมูลที่กำหนดขอบเขต ซึ่งหมายความว่าแอปพลิเคชันอื่นๆ ที่มีสิทธิ์ READ_EXTERNAL_STORAGE จะเข้าถึงข้อมูลใดก็ตามที่จัดเก็บไว้ในพื้นที่เก็บข้อมูลภายนอกได้

ผลกระทบ

ในแอปพลิเคชันที่กำหนดเป้าหมายเป็น Android 10 (API 29) หรือต่ำกว่า หากจัดเก็บข้อมูลที่ละเอียดอ่อนไว้ในพื้นที่เก็บข้อมูลภายนอก แอปพลิเคชันใดก็ตามในอุปกรณ์ที่มีสิทธิ์ READ_EXTERNAL_STORAGE จะเข้าถึงข้อมูลดังกล่าวได้ ซึ่งจะทำให้แอปพลิเคชันที่เป็นอันตรายเข้าถึงไฟล์ที่ละเอียดอ่อนซึ่งจัดเก็บไว้ในพื้นที่เก็บข้อมูลภายนอกอย่างถาวรหรือชั่วคราวได้โดยไม่แสดงการแจ้งเตือน นอกจากนี้ เนื่องจากแอปใดก็ตามในระบบเข้าถึงเนื้อหาในพื้นที่เก็บข้อมูลภายนอกได้ แอปพลิเคชันที่เป็นอันตรายที่ประกาศสิทธิ์ WRITE_EXTERNAL_STORAGE ด้วยจึงสามารถแก้ไขไฟล์ที่จัดเก็บไว้ในพื้นที่เก็บข้อมูลภายนอกได้ เช่น การใส่ข้อมูลที่เป็นอันตราย ข้อมูลที่เป็นอันตรายนี้อาจได้รับการออกแบบมาเพื่อหลอกลวงผู้ใช้หรือแม้แต่ทำให้เกิดการดำเนินการโค้ดได้หากโหลดลงในแอปพลิเคชัน

การบรรเทาผลกระทบ

พื้นที่เก็บข้อมูลที่กำหนดขอบเขต (Android 10 ขึ้นไป)

Android 10

สำหรับแอปพลิเคชันที่กำหนดเป้าหมายเป็น Android 10 นักพัฒนาแอปสามารถเลือกใช้พื้นที่เก็บข้อมูลที่กำหนดขอบเขตได้อย่างชัดเจน โดยตั้งค่าการแจ้ง requestLegacyExternalStorage เป็น false ในไฟล์ AndroidManifest.xml เมื่อใช้พื้นที่เก็บข้อมูลที่กำหนดขอบเขต แอปพลิเคชันจะเข้าถึงได้เฉพาะ ไฟล์ที่สร้างขึ้นเองในพื้นที่เก็บข้อมูลภายนอกหรือไฟล์ประเภท ที่จัดเก็บไว้โดยใช้ MediaStore API เช่น ไฟล์เสียงและวิดีโอ ซึ่งจะช่วยปกป้องความเป็นส่วนตัวและความปลอดภัยของผู้ใช้

Android 11 ขึ้นไป

สำหรับแอปพลิเคชันที่กำหนดเป้าหมายเป็น Android 11 ขึ้นไป ระบบปฏิบัติการจะบังคับใช้ พื้นที่เก็บข้อมูลที่กำหนดขอบเขต กล่าวคือ ระบบจะละเว้นการแจ้ง requestLegacyExternalStorage และปกป้อง พื้นที่เก็บข้อมูลภายนอกของแอปพลิเคชันจากการเข้าถึงที่ไม่พึงประสงค์โดยอัตโนมัติ

ใช้พื้นที่เก็บข้อมูลภายในสำหรับข้อมูลที่ละเอียดอ่อน

แอปพลิเคชันควรจัดเก็บข้อมูลที่ละเอียดอ่อนไว้ในที่จัดเก็บข้อมูลภายในเสมอ ไม่ว่าจะเป็น Android เวอร์ชันใดก็ตาม การเข้าถึงที่จัดเก็บข้อมูลภายในจะจำกัดไว้เฉพาะแอปพลิเคชันที่เป็นเจ้าของโดยอัตโนมัติด้วยแซนด์บ็อกซ์ของ Android จึงถือว่าปลอดภัย เว้นแต่ว่าอุปกรณ์จะได้รับการรูท

เข้ารหัสข้อมูลที่ละเอียดอ่อน

หากกรณีการใช้งานของแอปพลิเคชันกำหนดให้ต้องจัดเก็บข้อมูลที่ละเอียดอ่อนไว้ในพื้นที่เก็บข้อมูลภายนอก คุณควรเข้ารหัสข้อมูลดังกล่าว เราขอแนะนำให้ใช้อัลกอริทึมการเข้ารหัสที่รัดกุม โดยใช้ 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!");
        }
    }
}

แหล่งข้อมูล