Dữ liệu nhạy cảm được lưu trữ trong bộ nhớ ngoài

Danh mục OWASP: MASVS-STORAGE: Bộ nhớ

Tổng quan

Ứng dụng nhắm đến Android 10 (API 29) trở xuống không thực thi phạm vi bộ nhớ. Điều này có nghĩa là mọi dữ liệu được lưu trữ trên bộ nhớ ngoài đều có thể được truy cập bởi bất kỳ ứng dụng nào khác bằng READ_EXTERNAL_STORAGE quyền.

Tác động

Trong các ứng dụng nhắm đến Android 10 (API 29) trở xuống, nếu dữ liệu nhạy cảm được lưu trữ trên bộ nhớ ngoài, thì mọi ứng dụng trên thiết bị có quyền READ_EXTERNAL_STORAGE đều có thể truy cập vào dữ liệu đó. Điều này cho phép các ứng dụng độc hại các ứng dụng dùng để truy cập vĩnh viễn hoặc tạm thời vào các tệp nhạy cảm mà không âm thầm được lưu trữ trên bộ nhớ ngoài. Ngoài ra, vì nội dung trên bộ nhớ có thể bị bất kỳ ứng dụng nào trên hệ thống, bất kỳ ứng dụng độc hại nào truy cập đồng thời khai báo rằng quyền MANAGE_EXTERNAL_STORAGE có thể can thiệp vào các tệp được lưu trữ trên bộ nhớ ngoài, ví dụ: để bao gồm dữ liệu độc hại. Ứng dụng độc hại dữ liệu, nếu được tải vào ứng dụng, có thể được thiết kế để đánh lừa người dùng hoặc thậm chí thực thi mã.

Giải pháp giảm thiểu

Bộ nhớ có giới hạn (Android 10 trở lên)

Android 10

Đối với các ứng dụng nhắm đến Android 10, nhà phát triển có thể chọn sử dụng bộ nhớ có giới hạn. Có thể thực hiện điều này bằng cách đặt requestLegacyExternalStorage gắn cờ thành false trong phần Tệp AndroidManifest.xml. Với bộ nhớ có giới hạn, các ứng dụng chỉ có thể truy cập các tệp mà họ đã tự tạo trên bộ nhớ ngoài hoặc các loại tệp đã được lưu trữ bằng API MediaStore, chẳng hạn như Âm thanh và Video. Việc này giúp bảo vệ quyền riêng tư và sự an toàn của người dùng.

Android 11 trở lên

Đối với các ứng dụng nhắm đến Android 11 trở lên, hệ điều hành bắt buộc sử dụng bộ nhớ có giới hạn, tức là bỏ qua cờ requestLegacyExternalStorage và tự động bảo vệ bộ nhớ ngoài của ứng dụng khỏi quyền truy cập không mong muốn.

Sử dụng bộ nhớ trong cho dữ liệu nhạy cảm

Bất kể phiên bản Android được nhắm đến là gì, dữ liệu nhạy cảm của ứng dụng phải luôn được lưu trữ trên bộ nhớ trong. Quyền truy cập vào bộ nhớ trong bây giờ là tự động bị giới hạn ở ứng dụng sở hữu nhờ hộp cát Android, do đó có thể được coi là bảo mật, trừ khi thiết bị đã bị can thiệp vào hệ thống.

Mã hoá dữ liệu nhạy cảm

Nếu các trường hợp sử dụng của ứng dụng yêu cầu lưu trữ dữ liệu nhạy cảm trên thì dữ liệu phải được mã hoá. Bạn nên sử dụng một thuật toán mã hoá mạnh, sử dụng Kho khoá Android để lưu trữ khoá một cách an toàn.

Nhìn chung, bạn nên mã hoá tất cả dữ liệu nhạy cảm, bất kể dữ liệu đó được lưu trữ ở đâu.

Điều quan trọng cần lưu ý là phương thức mã hoá toàn bộ đĩa (hoặc mã hoá dựa trên tệp từ Android 10) là một biện pháp nhằm bảo vệ dữ liệu khỏi hành vi tiếp cận thông tin cá nhân và vectơ tấn công. Do đó, để áp dụng cùng một biện pháp bảo mật, ứng dụng cũng phải mã hoá dữ liệu nhạy cảm được lưu trữ trên bộ nhớ ngoài.

Kiểm tra tính toàn vẹn

Trong trường hợp dữ liệu hoặc mã phải được tải từ bộ nhớ ngoài vào ứng dụng, kiểm tra tính toàn vẹn để xác minh rằng không có ứng dụng nào khác bị can thiệp bằng dữ liệu hoặc mã này. Cần lưu trữ hàm băm của các tệp một cách an toàn, tốt nhất là được mã hoá và lưu vào bộ nhớ trong.

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

Tài nguyên