Tải mã động

Danh mục OWASP: MASVS-CODE: Chất lượng mã

Tổng quan

Việc tải mã một cách linh hoạt vào ứng dụng sẽ làm tăng mức độ rủi ro cần phải giảm thiểu. Kẻ tấn công có thể giả mạo hoặc thay thế mã để truy cập vào dữ liệu nhạy cảm hoặc thực hiện các hành động gây hại.

Nhiều hình thức tải mã động, đặc biệt là những hình thức sử dụng các nguồn từ xa, vi phạm chính sách của Google Play và có thể dẫn đến việc ứng dụng của bạn bị tạm ngưng trên Google Play.

Tác động

Nếu kẻ tấn công tìm cách truy cập vào mã sẽ được tải vào ứng dụng, chúng có thể sửa đổi mã đó để hỗ trợ mục tiêu của chúng. Điều này có thể dẫn đến việc đánh cắp dữ liệu và khai thác thực thi mã. Ngay cả khi kẻ tấn công không thể sửa đổi mã để thực hiện các hành động tuỳ ý mà chúng chọn, thì vẫn có khả năng chúng có thể làm hỏng hoặc xoá mã và do đó ảnh hưởng đến tính khả dụng của ứng dụng.

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

Tránh sử dụng tính năng tải mã động

Tránh tải mã động, trừ phi có nhu cầu kinh doanh. Bạn nên ưu tiên đưa tất cả các chức năng trực tiếp vào ứng dụng, bất cứ khi nào có thể.

Sử dụng nguồn đáng tin cậy

Mã sẽ được tải vào ứng dụng phải được lưu trữ ở các vị trí đáng tin cậy. Đối với bộ nhớ cục bộ, bộ nhớ trong của ứng dụng hoặc bộ nhớ có giới hạn (đối với Android 10 trở lên) là những nơi được đề xuất. Những vị trí này có các biện pháp để tránh truy cập trực tiếp từ các ứng dụng và người dùng khác.

Khi tải mã từ các vị trí từ xa như URL, hãy tránh sử dụng bên thứ ba khi có thể và lưu trữ mã trong cơ sở hạ tầng của riêng bạn, tuân theo các phương pháp hay nhất về bảo mật. Nếu bạn cần tải mã của bên thứ ba, hãy đảm bảo rằng nhà cung cấp đó là một nhà cung cấp đáng tin cậy.

Thực hiện kiểm tra tính toàn vẹn

Bạn nên kiểm tra tính toàn vẹn để đảm bảo mã chưa bị giả mạo. Bạn nên thực hiện các bước kiểm tra này trước khi tải mã vào ứng dụng.

Khi tải tài nguyên từ xa, bạn có thể sử dụng tính toàn vẹn của tài nguyên phụ để xác thực tính toàn vẹn của các tài nguyên được truy cập.

Khi tải tài nguyên từ bộ nhớ ngoài, hãy sử dụng các quy trình kiểm tra tính toàn vẹn để xác minh rằng không có ứng dụng nào khác can thiệp vào dữ liệu hoặc mã này. Bạn nê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à mã hoá và lưu trữ trong 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(bytes.length * 2)
        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(bytes.length * 2);
        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!");
        }
    }
}

Ký mã

Một lựa chọn khác để đảm bảo tính toàn vẹn của dữ liệu là ký mã và xác minh chữ ký của mã trước khi tải mã. Phương thức này cũng có ưu điểm là đảm bảo tính toàn vẹn của mã băm, chứ không chỉ mã, giúp tăng cường khả năng chống giả mạo.

Mặc dù tính năng ký mã cung cấp thêm các lớp bảo mật, nhưng bạn cần lưu ý rằng đây là một quy trình phức tạp hơn và có thể đòi hỏi thêm nỗ lực và tài nguyên để triển khai thành công.

Bạn có thể xem một số ví dụ về việc ký mã trong phần Tài nguyên của tài liệu này.

Tài nguyên