动态代码加载

OWASP 类别:MASVS-CODE:代码质量

概览

动态将代码加载到应用中会引入必须降低的风险级别。攻击者可能会篡改或替换代码,以访问敏感数据或执行有害操作。

许多形式的动态代码加载(尤其是使用远程来源的加载方式)违反了 Google Play 政策,可能会导致您的应用在 Google Play 中被暂停。

影响

如果攻击者设法获得对要加载到应用中的代码的访问权限,则可以修改该代码以实现其目标。这可能会导致数据渗漏和代码执行漏洞。即使攻击者无法修改代码以执行他们选择的任意操作,也有可能破坏或移除代码,从而影响应用的可用性。

缓解措施

避免使用动态代码加载

除非有业务需求,否则请避免动态代码加载。您应尽可能直接将所有功能添加到应用中。

使用可信来源

要加载到应用中的代码应存储在可信位置。对于本地存储空间,建议使用应用内部存储空间或分区存储空间(适用于 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(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!");
        }
    }
}

对代码进行签名

确保数据完整性的另一种方法是在加载代码之前对其进行签名并验证其签名。这种方法的优势在于,不仅能确保代码本身的完整性,还能确保哈希代码的完整性,从而提供额外的防篡改保护。

虽然代码签名可提供额外的安全层,但请务必注意,这是一个更复杂的过程,可能需要额外的努力和资源才能成功实现。

您可以在本文档的“资源”部分找到一些代码签名示例。

资源