หมวดหมู่ OWASP: MASVS-STORAGE: พื้นที่เก็บข้อมูล
ภาพรวม
แอปพลิเคชันที่กําหนดเป้าหมายเป็น Android 10 (API ระดับ 29) หรือต่ำกว่าจะไม่บังคับใช้ scoped
storage ซึ่งหมายความว่าข้อมูลทั้งหมดที่เก็บไว้ในที่จัดเก็บข้อมูลภายนอกสามารถ
เข้าถึงโดยแอปพลิเคชันอื่นที่มี 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 ขึ้นไป ระบบปฏิบัติการจะบังคับใช้การใช้พื้นที่เก็บข้อมูลแบบจำกัด กล่าวคือ ระบบจะไม่สนใจ Flag requestLegacyExternalStorage และปกป้องพื้นที่เก็บข้อมูลภายนอกของแอปพลิเคชันจากการเข้าถึงที่ไม่พึงประสงค์โดยอัตโนมัติ
ใช้พื้นที่เก็บข้อมูลภายในสำหรับข้อมูลที่ละเอียดอ่อน
ไม่ว่า Android เวอร์ชันเป้าหมายจะเป็นเวอร์ชันใด ข้อมูลที่ละเอียดอ่อนของแอปพลิเคชันควรจัดเก็บไว้ในที่จัดเก็บข้อมูลภายในเสมอ การเข้าถึงที่จัดเก็บข้อมูลภายในปัจจุบันคือ จำกัดไว้เฉพาะแอปพลิเคชันที่เป็นเจ้าของโดยอัตโนมัติด้วยแซนด์บ็อกซ์ของ Android ดังนั้นจึงถือว่าอุปกรณ์ปลอดภัย เว้นแต่อุปกรณ์จะได้รับการรูท
เข้ารหัสข้อมูลที่ละเอียดอ่อน
หาก Use Case ของแอปพลิเคชันกำหนดให้จัดเก็บข้อมูลที่ละเอียดอ่อนไว้ในพื้นที่เก็บข้อมูลภายนอก ข้อมูลดังกล่าวควรได้รับการเข้ารหัส เราขอแนะนำให้ใช้อัลกอริทึมการเข้ารหัสที่รัดกุมโดยใช้ 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!");
        }
    }
}
แหล่งข้อมูล
- พื้นที่เก็บข้อมูลที่กำหนดขอบเขต
 - READ_EXTERNAL_STORAGE
 - WRITE_EXTERNAL_STORAGE
 - requestLegacyExternalStorage
 - ภาพรวมของพื้นที่เก็บข้อมูลและไฟล์
 - พื้นที่เก็บข้อมูล (เฉพาะแอป)
 - วิทยาการเข้ารหัสลับ
 - คีย์สโตร์
 - การเข้ารหัสตามไฟล์
 - การเข้ารหัสแบบ Full-Disk