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 động vào ứng dụng sẽ làm tăng mức độ rủi ro mà bạn phải giảm thiểu. Kẻ tấn công có thể can thiệp 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 nguồn từ xa, vi phạm chính sách của Google Play và có thể khiến ứ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 có thể truy cập vào mã sẽ được tải vào ứng dụng, thì chúng có thể sửa đổi mã đó để hỗ trợ mục tiêu của mình. Đ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 không thể sửa đổi mã để thực hiện các hành động tuỳ ý, kẻ tấn công vẫn có thể làm hỏng hoặc xoá mã, từ đó ảnh hưởng đến khả năng hoạt độ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ừ khi có nhu cầu kinh doanh, hãy tránh tải mã động. Bạn nên đưa tất 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ạn nên sử dụng bộ nhớ trong của ứng dụng hoặc bộ nhớ có giới hạn (dành cho Android 10 trở lên). Các vị trí này có các biện pháp để tránh quyền 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.

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 rằng mã không bị can thiệp. 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 tài nguyên đã truy cập.

Khi tải tài nguyên từ bộ nhớ ngoài, hãy sử dụng tính nă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 can thiệp vào dữ liệu hoặc mã này. Hàm băm của các tệp phải được lưu trữ một cách an toàn, tốt nhất là được mã hoá và 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 cách 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ó ưu điểm là cũng đảm bảo tính toàn vẹn của mã băm, không chỉ mã đó, mà còn cung cấp thêm một biện pháp bảo vệ chống can thiệp.

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

Bạn có thể tìm thấy 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